multiverse/widgets/recovery/
mod.rs
1use crossterm::event::{KeyCode, KeyEvent};
2use matrix_sdk::{encryption::recovery::RecoveryState, Client};
3use ratatui::prelude::*;
4use recovering::RecoveringView;
5use throbber_widgets_tui::{Throbber, ThrobberState};
6
7mod default;
8mod recovering;
9
10use default::DefaultRecoveryView;
11
12#[derive(Default)]
13pub struct RecoveryView {}
14
15impl RecoveryView {
16 pub fn new() -> Self {
17 Self {}
18 }
19}
20
21impl RecoveryView {}
22
23pub struct RecoveryViewState {
24 client: Client,
25 throbber_state: ThrobberState,
26 mode: Mode,
27}
28
29#[derive(Debug, Default)]
30enum Mode {
31 #[default]
32 Unknown,
33 Incomplete {
34 view: RecoveringView,
35 },
36 Default {
37 view: DefaultRecoveryView,
38 },
39}
40
41pub enum ShouldExit {
42 No,
43 OnlySubScreen,
44 Yes,
45}
46
47impl RecoveryViewState {
48 pub fn new(client: Client) -> Self {
49 Self { client, throbber_state: ThrobberState::default(), mode: Mode::default() }
50 }
51
52 fn update_state(&mut self) {
53 let recovery_state = self.client.encryption().recovery().state();
54
55 match (&mut self.mode, recovery_state) {
56 (Mode::Unknown, RecoveryState::Disabled | RecoveryState::Enabled) => {
61 self.mode = Mode::Default { view: DefaultRecoveryView::new(self.client.clone()) };
62 }
63
64 (Mode::Unknown, RecoveryState::Incomplete) => {
67 let view = RecoveringView::new(self.client.clone());
68 self.mode = Mode::Incomplete { view }
69 }
70
71 (Mode::Incomplete { view }, RecoveryState::Disabled) => {
74 if view.is_idle() {
75 self.mode =
76 Mode::Default { view: DefaultRecoveryView::new(self.client.clone()) }
77 }
78 }
79
80 (Mode::Incomplete { view }, RecoveryState::Enabled) => {
81 if view.is_idle() {
82 self.mode =
83 Mode::Default { view: DefaultRecoveryView::new(self.client.clone()) }
84 }
85 }
86
87 (Mode::Default { view }, RecoveryState::Incomplete) => {
88 if view.is_idle() {
89 let view = RecoveringView::new(self.client.clone());
90 self.mode = Mode::Incomplete { view }
91 }
92 }
93
94 (Mode::Incomplete { .. }, RecoveryState::Incomplete)
96 | (Mode::Default { .. }, RecoveryState::Disabled | RecoveryState::Enabled)
97 | (Mode::Unknown, RecoveryState::Unknown) => {}
98
99 (Mode::Default { .. }, RecoveryState::Unknown)
103 | (Mode::Incomplete { .. }, RecoveryState::Unknown) => {
104 self.mode = Mode::Unknown;
105 }
106 }
107 }
108
109 pub async fn handle_key_press(&mut self, key: KeyEvent) -> bool {
110 use KeyCode::*;
111
112 match &mut self.mode {
113 Mode::Unknown => matches!((key.modifiers, key.code), (_, Esc | Char('q'))),
114 Mode::Incomplete { view } => match view.handle_key(key) {
115 ShouldExit::No => false,
116 ShouldExit::OnlySubScreen => {
117 self.mode = Mode::Unknown;
118 false
119 }
120 ShouldExit::Yes => true,
121 },
122 Mode::Default { view } => match view.handle_key(key).await {
123 ShouldExit::No => false,
124 ShouldExit::OnlySubScreen => {
125 self.mode = Mode::Unknown;
126 false
127 }
128 ShouldExit::Yes => true,
129 },
130 }
131 }
132
133 pub fn on_tick(&mut self) {
134 self.throbber_state.calc_next();
135
136 match &mut self.mode {
137 Mode::Unknown => (),
138 Mode::Incomplete { view } => view.on_tick(),
139 Mode::Default { view } => view.on_tick(),
140 }
141 }
142
143 fn get_throbber(&self, title: &'static str) -> Throbber<'static> {
144 Throbber::default().label(title).throbber_set(throbber_widgets_tui::BRAILLE_EIGHT_DOUBLE)
145 }
146}
147
148pub fn create_centered_throbber_area(area: Rect) -> Rect {
149 let chunks = Layout::default()
150 .direction(Direction::Horizontal)
151 .margin(1)
152 .constraints([Constraint::Fill(1), Constraint::Length(12), Constraint::Fill(1)])
153 .split(area);
154
155 let chunks = Layout::default()
156 .direction(Direction::Vertical)
157 .margin(1)
158 .constraints([Constraint::Fill(1), Constraint::Length(1), Constraint::Fill(1)])
159 .split(chunks[1]);
160
161 chunks[1]
162}
163
164impl StatefulWidget for &mut RecoveryView {
165 type State = RecoveryViewState;
166
167 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
168 state.update_state();
169
170 match &mut state.mode {
172 Mode::Unknown => {
173 let throbber = state.get_throbber("Loading");
174 let centered_area = create_centered_throbber_area(area);
175 StatefulWidget::render(throbber, centered_area, buf, &mut state.throbber_state);
176 }
177 Mode::Default { view } => {
178 view.render(area, buf);
179 }
180 Mode::Incomplete { view } => {
181 view.render(area, buf);
182 }
183 }
184 }
185}