multiverse/widgets/recovery/
mod.rs1use crossterm::event::{KeyCode, KeyEvent};
2use matrix_sdk::{Client, encryption::recovery::RecoveryState};
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) => {
63 self.mode = Mode::Default { view: DefaultRecoveryView::new(self.client.clone()) };
64 }
65
66 (Mode::Unknown, RecoveryState::Incomplete) => {
70 let view = RecoveringView::new(self.client.clone());
71 self.mode = Mode::Incomplete { view }
72 }
73
74 (Mode::Incomplete { view }, RecoveryState::Disabled) => {
77 if view.is_idle() {
78 self.mode =
79 Mode::Default { view: DefaultRecoveryView::new(self.client.clone()) }
80 }
81 }
82
83 (Mode::Incomplete { view }, RecoveryState::Enabled) => {
84 if view.is_idle() {
85 self.mode =
86 Mode::Default { view: DefaultRecoveryView::new(self.client.clone()) }
87 }
88 }
89
90 (Mode::Default { view }, RecoveryState::Incomplete) => {
91 if view.is_idle() {
92 let view = RecoveringView::new(self.client.clone());
93 self.mode = Mode::Incomplete { view }
94 }
95 }
96
97 (Mode::Incomplete { .. }, RecoveryState::Incomplete)
99 | (Mode::Default { .. }, RecoveryState::Disabled | RecoveryState::Enabled)
100 | (Mode::Unknown, RecoveryState::Unknown) => {}
101
102 (Mode::Default { .. }, RecoveryState::Unknown)
106 | (Mode::Incomplete { .. }, RecoveryState::Unknown) => {
107 self.mode = Mode::Unknown;
108 }
109 }
110 }
111
112 pub async fn handle_key_press(&mut self, key: KeyEvent) -> bool {
113 use KeyCode::*;
114
115 match &mut self.mode {
116 Mode::Unknown => matches!((key.modifiers, key.code), (_, Esc | Char('q'))),
117 Mode::Incomplete { view } => match view.handle_key(key) {
118 ShouldExit::No => false,
119 ShouldExit::OnlySubScreen => {
120 self.mode = Mode::Unknown;
121 false
122 }
123 ShouldExit::Yes => true,
124 },
125 Mode::Default { view } => match view.handle_key(key).await {
126 ShouldExit::No => false,
127 ShouldExit::OnlySubScreen => {
128 self.mode = Mode::Unknown;
129 false
130 }
131 ShouldExit::Yes => true,
132 },
133 }
134 }
135
136 pub fn on_tick(&mut self) {
137 self.throbber_state.calc_next();
138
139 match &mut self.mode {
140 Mode::Unknown => (),
141 Mode::Incomplete { view } => view.on_tick(),
142 Mode::Default { view } => view.on_tick(),
143 }
144 }
145
146 fn get_throbber(&self, title: &'static str) -> Throbber<'static> {
147 Throbber::default().label(title).throbber_set(throbber_widgets_tui::BRAILLE_EIGHT_DOUBLE)
148 }
149}
150
151pub fn create_centered_throbber_area(area: Rect) -> Rect {
152 let chunks = Layout::default()
153 .direction(Direction::Horizontal)
154 .margin(1)
155 .constraints([Constraint::Fill(1), Constraint::Length(12), Constraint::Fill(1)])
156 .split(area);
157
158 let chunks = Layout::default()
159 .direction(Direction::Vertical)
160 .margin(1)
161 .constraints([Constraint::Fill(1), Constraint::Length(1), Constraint::Fill(1)])
162 .split(chunks[1]);
163
164 chunks[1]
165}
166
167impl StatefulWidget for &mut RecoveryView {
168 type State = RecoveryViewState;
169
170 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
171 state.update_state();
172
173 match &mut state.mode {
175 Mode::Unknown => {
176 let throbber = state.get_throbber("Loading");
177 let centered_area = create_centered_throbber_area(area);
178 StatefulWidget::render(throbber, centered_area, buf, &mut state.throbber_state);
179 }
180 Mode::Default { view } => {
181 view.render(area, buf);
182 }
183 Mode::Incomplete { view } => {
184 view.render(area, buf);
185 }
186 }
187 }
188}