1use tokio::sync::{
4 broadcast,
5 mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel},
6};
7
8use crate::{termination::Interrupted, ui::components::content_view::ActiveView};
9
10use super::action::ViewAction;
11
12#[allow(clippy::module_name_repetitions)]
14pub struct ViewState {
15 state_tx: UnboundedSender<ActiveView>,
16}
17
18impl ViewState {
19 #[must_use]
21 pub fn new() -> (Self, UnboundedReceiver<ActiveView>) {
22 let (state_tx, state_rx) = unbounded_channel::<ActiveView>();
23 (Self { state_tx }, state_rx)
24 }
25
26 pub async fn main_loop(
32 &self,
33 mut action_rx: UnboundedReceiver<ViewAction>,
34 mut interrupt_rx: broadcast::Receiver<Interrupted>,
35 ) -> anyhow::Result<Interrupted> {
36 let mut state = ActiveView::default();
37 let mut view_history = vec![state.clone()];
39 let mut view_index = 0;
40
41 self.state_tx.send(state.clone())?;
43
44 let result = loop {
45 tokio::select! {
46 Some(action) = action_rx.recv() => {
49 state = self.handle_action(&mut view_history, &mut view_index, action);
50 self.state_tx.send(state.clone())?;
51 },
52 Ok(interrupted) = interrupt_rx.recv() => {
54 break interrupted;
55 }
56 }
57 };
58
59 Ok(result)
60 }
61
62 pub fn handle_action(
64 &self,
65 view_history: &mut Vec<ActiveView>,
66 view_index: &mut usize,
67 action: ViewAction,
68 ) -> ActiveView {
69 match action {
70 ViewAction::Set(view) => {
71 view_history.truncate(*view_index + 1);
72 view_history.push(view);
73 *view_index = view_history.len() - 1;
74 }
75 ViewAction::Back if *view_index > 0 => *view_index -= 1,
76 ViewAction::Next if *view_index < view_history.len() - 1 => *view_index += 1,
77 _ => {}
78 }
79 view_history.get(*view_index).cloned().unwrap_or_default()
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use pretty_assertions::assert_eq;
87
88 #[test]
89 fn test_handle_action() {
90 let (view, _) = ViewState::new();
91
92 let mut view_history = vec![ActiveView::default()];
93 let mut view_index = 0;
94
95 let mut state = view.handle_action(
96 &mut view_history,
97 &mut view_index,
98 ViewAction::Set(ActiveView::Search),
99 );
100 assert_eq!(state, ActiveView::Search);
101
102 state = view.handle_action(
103 &mut view_history,
104 &mut view_index,
105 ViewAction::Set(ActiveView::Songs),
106 );
107 assert_eq!(state, ActiveView::Songs);
108
109 state = view.handle_action(
110 &mut view_history,
111 &mut view_index,
112 ViewAction::Set(ActiveView::Artists),
113 );
114 assert_eq!(state, ActiveView::Artists);
115
116 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
117 assert_eq!(state, ActiveView::Songs);
118
119 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
120 assert_eq!(state, ActiveView::Search);
121
122 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
123 assert_eq!(state, ActiveView::default());
124
125 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
126 assert_eq!(state, ActiveView::default());
127 }
128
129 #[test]
130 fn test_forward_backward() {
131 let (view, _) = ViewState::new();
132
133 let mut view_history = vec![ActiveView::default()];
134 let mut view_index = 0;
135
136 let mut state = view.handle_action(
137 &mut view_history,
138 &mut view_index,
139 ViewAction::Set(ActiveView::Search),
140 );
141 assert_eq!(state, ActiveView::Search);
142
143 state = view.handle_action(
144 &mut view_history,
145 &mut view_index,
146 ViewAction::Set(ActiveView::Songs),
147 );
148 assert_eq!(state, ActiveView::Songs);
149
150 state = view.handle_action(
151 &mut view_history,
152 &mut view_index,
153 ViewAction::Set(ActiveView::Artists),
154 );
155 assert_eq!(state, ActiveView::Artists);
156
157 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
158 assert_eq!(state, ActiveView::Songs);
159
160 state = view.handle_action(
161 &mut view_history,
162 &mut view_index,
163 ViewAction::Set(ActiveView::Albums),
164 );
165 assert_eq!(state, ActiveView::Albums);
166
167 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
168 assert_eq!(state, ActiveView::Songs);
169
170 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Next);
171 assert_eq!(state, ActiveView::Albums);
172
173 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Next);
174 assert_eq!(state, ActiveView::Albums);
175
176 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
177 assert_eq!(state, ActiveView::Songs);
178
179 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
180 assert_eq!(state, ActiveView::Search);
181
182 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
183 assert_eq!(state, ActiveView::default());
184
185 state = view.handle_action(&mut view_history, &mut view_index, ViewAction::Back);
186 assert_eq!(state, ActiveView::default());
187 }
188}