Skip to content

Commit e278aaa

Browse files
committed
chore: toast queue manager
1 parent 3cc5501 commit e278aaa

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package to.bitkit.ui.shared.toast
2+
3+
import kotlinx.coroutines.CoroutineScope
4+
import kotlinx.coroutines.Job
5+
import kotlinx.coroutines.delay
6+
import kotlinx.coroutines.flow.MutableStateFlow
7+
import kotlinx.coroutines.flow.StateFlow
8+
import kotlinx.coroutines.flow.asStateFlow
9+
import kotlinx.coroutines.flow.update
10+
import kotlinx.coroutines.launch
11+
import to.bitkit.models.Toast
12+
13+
private const val MAX_QUEUE_SIZE = 5
14+
15+
/**
16+
* Manages a queue of toasts to display sequentially.
17+
*
18+
* This ensures that toasts are shown one at a time without premature cancellation.
19+
* When a toast is displayed, it waits for its full visibility duration before
20+
* showing the next toast in the queue.
21+
*
22+
* Features:
23+
* - Thread-safe queue using StateFlow
24+
* - Sequential display (one toast at a time)
25+
* - Pause/resume timer on drag interactions
26+
* - Auto-advance to next toast on completion
27+
* - Max queue size with FIFO overflow handling
28+
*/
29+
class ToastQueueManager(private val scope: CoroutineScope) {
30+
// Public state exposed to UI
31+
private val _currentToast = MutableStateFlow<Toast?>(null)
32+
val currentToast: StateFlow<Toast?> = _currentToast.asStateFlow()
33+
34+
private val _queueSize = MutableStateFlow(0)
35+
val queueSize: StateFlow<Int> = _queueSize.asStateFlow()
36+
37+
// Internal queue state
38+
private val _queue = MutableStateFlow<List<Toast>>(emptyList())
39+
private var timerJob: Job? = null
40+
private var isPaused = false
41+
42+
/**
43+
* Add toast to queue. If queue is full, drops oldest.
44+
*/
45+
fun enqueue(toast: Toast) {
46+
_queue.update { current ->
47+
val newQueue = if (current.size >= MAX_QUEUE_SIZE) {
48+
// Drop oldest (first item) when queue full
49+
current.drop(1) + toast
50+
} else {
51+
current + toast
52+
}
53+
_queueSize.value = newQueue.size
54+
newQueue
55+
}
56+
// If no toast is currently displayed, show this one immediately
57+
showNextToastIfAvailable()
58+
}
59+
60+
/**
61+
* Dismiss current toast and advance to next in queue.
62+
*/
63+
fun dismissCurrentToast() {
64+
cancelTimer()
65+
_currentToast.value = null
66+
isPaused = false
67+
// Check if there are more toasts waiting and show next one
68+
showNextToastIfAvailable()
69+
}
70+
71+
/**
72+
* Pause current toast timer (called on drag start).
73+
*/
74+
fun pauseCurrentToast() {
75+
if (_currentToast.value?.autoHide == true) {
76+
isPaused = true
77+
cancelTimer()
78+
}
79+
}
80+
81+
/**
82+
* Resume current toast timer with FULL duration (called on drag end).
83+
*/
84+
fun resumeCurrentToast() {
85+
if (isPaused && _currentToast.value != null) {
86+
isPaused = false
87+
val toast = _currentToast.value
88+
if (toast?.autoHide == true) {
89+
startTimer(toast.visibilityTime)
90+
}
91+
}
92+
}
93+
94+
/**
95+
* Clear all queued toasts and hide current toast.
96+
*/
97+
fun clear() {
98+
cancelTimer()
99+
_queue.value = emptyList()
100+
_queueSize.value = 0
101+
_currentToast.value = null
102+
isPaused = false
103+
}
104+
105+
/**
106+
* Show next toast from queue and start its timer.
107+
*/
108+
private fun showNextToast() {
109+
val nextToast = _queue.value.firstOrNull() ?: return
110+
111+
// Remove from queue
112+
_queue.update { it.drop(1) }
113+
_queueSize.value = _queue.value.size
114+
115+
// Display toast
116+
_currentToast.value = nextToast
117+
isPaused = false
118+
119+
// Start auto-hide timer if enabled
120+
if (nextToast.autoHide) {
121+
startTimer(nextToast.visibilityTime)
122+
}
123+
}
124+
125+
/**
126+
* Start auto-hide timer for current toast.
127+
*/
128+
private fun startTimer(duration: Long) {
129+
cancelTimer()
130+
timerJob = scope.launch {
131+
delay(duration)
132+
if (!isPaused) {
133+
_currentToast.value = null
134+
// Show next toast if available
135+
showNextToastIfAvailable()
136+
}
137+
}
138+
}
139+
140+
/**
141+
* Show next toast if queue is not empty and no toast is currently displayed.
142+
*/
143+
private fun showNextToastIfAvailable() {
144+
if (_currentToast.value == null && _queue.value.isNotEmpty()) {
145+
showNextToast()
146+
}
147+
}
148+
149+
/**
150+
* Cancel current timer (idempotent).
151+
*/
152+
private fun cancelTimer() {
153+
timerJob?.cancel()
154+
timerJob = null
155+
}
156+
}

0 commit comments

Comments
 (0)