multiverse/widgets/settings/
mod.rs
1use std::sync::Arc;
2
3use crossterm::event::{KeyCode, KeyEvent};
4use developer::DeveloperSettingsView;
5use matrix_sdk::Client;
6use matrix_sdk_ui::sync_service::SyncService;
7use ratatui::{prelude::*, widgets::*};
8use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
9use style::palette::tailwind;
10
11use super::recovery::{RecoveryView, RecoveryViewState};
12use crate::popup_area;
13
14mod developer;
15
16#[derive(Clone, Copy, Default, Display, FromRepr, EnumIter)]
20enum SelectedTab {
21 #[default]
23 Developer,
24
25 Encryption,
27}
28
29impl SelectedTab {
30 fn previous(self) -> Self {
33 let current_index: usize = self as usize;
34 let previous_index = current_index.saturating_sub(1);
35 Self::from_repr(previous_index).unwrap_or(self)
36 }
37
38 fn next(self) -> Self {
40 let current_index = self as usize;
41 let next_index = current_index.saturating_add(1);
42 Self::from_repr(next_index).unwrap_or(self)
43 }
44
45 fn cycle_next(self) -> Self {
48 let current_index = self as usize;
49 let next_index = current_index.saturating_add(1);
50 Self::from_repr(next_index).unwrap_or_default()
51 }
52
53 fn cycle_prev(self) -> Self {
56 let current_index = self as usize;
57
58 if current_index == 0 {
59 Self::iter().next_back().expect("We should always have a last element in our enum")
60 } else {
61 let previous_index = current_index.saturating_sub(1);
62 Self::from_repr(previous_index).unwrap_or(self)
63 }
64 }
65
66 fn title(self) -> Line<'static> {
68 format!(" {self} ").fg(tailwind::SLATE.c200).bg(self.palette().c900).into()
69 }
70
71 const fn palette(&self) -> tailwind::Palette {
72 match self {
73 Self::Developer => tailwind::BLUE,
74 Self::Encryption => tailwind::EMERALD,
75 }
76 }
77}
78
79pub struct SettingsView {
80 selected_tab: SelectedTab,
81
82 developer_settings_view: DeveloperSettingsView,
83 recovery_view_state: RecoveryViewState,
84}
85
86impl SettingsView {
87 pub fn new(client: Client, sync_service: Arc<SyncService>) -> Self {
88 let recovery_view_state = RecoveryViewState::new(client.clone());
89 let developer_settings_view = DeveloperSettingsView::new(client, sync_service);
90
91 Self { selected_tab: SelectedTab::default(), recovery_view_state, developer_settings_view }
92 }
93
94 pub async fn handle_key_press(&mut self, event: KeyEvent) -> bool {
95 use KeyCode::*;
96
97 match event.code {
98 Right => {
99 self.next_tab();
100 false
101 }
102
103 Tab => {
104 self.cycle_next_tab();
105 false
106 }
107
108 BackTab => {
109 self.cycle_prev_tab();
110 false
111 }
112
113 Left => {
114 self.previous_tab();
115 false
116 }
117
118 Char('q') | Esc => match self.selected_tab {
119 SelectedTab::Developer => true,
120 SelectedTab::Encryption => self.recovery_view_state.handle_key_press(event).await,
121 },
122
123 _ => match self.selected_tab {
124 SelectedTab::Developer => {
125 self.developer_settings_view.handle_key_press(event).await;
126 false
127 }
128 SelectedTab::Encryption => self.recovery_view_state.handle_key_press(event).await,
129 },
130 }
131 }
132
133 pub fn on_tick(&mut self) {
134 self.recovery_view_state.on_tick();
135 }
136
137 fn cycle_next_tab(&mut self) {
138 self.selected_tab = self.selected_tab.cycle_next();
139 }
140
141 fn cycle_prev_tab(&mut self) {
142 self.selected_tab = self.selected_tab.cycle_prev();
143 }
144
145 fn next_tab(&mut self) {
146 self.selected_tab = self.selected_tab.next();
147 }
148
149 fn previous_tab(&mut self) {
150 self.selected_tab = self.selected_tab.previous();
151 }
152}
153
154impl Widget for &mut SettingsView {
155 fn render(self, area: Rect, buf: &mut Buffer)
156 where
157 Self: Sized,
158 {
159 use Constraint::{Length, Min};
160
161 let area = popup_area(area, 40, 30);
162 Clear.render(area, buf);
163
164 let vertical = Layout::vertical([Length(1), Min(0), Length(1)]);
165 let [header_area, inner_area, footer_area] = vertical.areas(area);
166
167 let horizontal = Layout::horizontal([Min(0), Length(20)]);
168 let [tab_title_area, title_area] = horizontal.areas(header_area);
169
170 "Settings".bold().render(title_area, buf);
171
172 Block::bordered()
173 .border_set(symbols::border::PROPORTIONAL_TALL)
174 .padding(Padding::horizontal(1))
175 .border_style(tailwind::BLUE.c700)
176 .render(inner_area, buf);
177
178 let titles = SelectedTab::iter().map(SelectedTab::title);
179 let highlight_style = (Color::default(), self.selected_tab.palette().c700);
180 let selected_tab_index = self.selected_tab as usize;
181
182 let tabs_area = inner_area.inner(Margin::new(1, 1));
183
184 Tabs::new(titles)
185 .highlight_style(highlight_style)
186 .select(selected_tab_index)
187 .padding("", "")
188 .divider(" ")
189 .render(tab_title_area, buf);
190
191 match self.selected_tab {
192 SelectedTab::Developer => {
193 self.developer_settings_view.render(tabs_area, buf);
194 }
195 SelectedTab::Encryption => {
196 let mut view = RecoveryView::new();
197 view.render(tabs_area, buf, &mut self.recovery_view_state);
198 }
199 }
200
201 Line::raw("◄ ► to change tab | Press q to exit the settings screen")
202 .centered()
203 .render(footer_area, buf);
204 }
205}