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// TODO: This replicates a lot of the logic the details view has, we should make
17// a generic tab popout widget to share a bit of logic here.
18
19#[derive(Clone, Copy, Default, Display, FromRepr, EnumIter)]
20enum SelectedTab {
21    /// Show the developer settings we have.
22    #[default]
23    Developer,
24
25    /// Show the encryption settings
26    Encryption,
27}
28
29impl SelectedTab {
30    /// Get the previous tab, if there is no previous tab return the current
31    /// tab.
32    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    /// Get the next tab, if there is no next tab return the current tab.
39    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    /// Cycle to the next tab, if we're at the last tab we return the first and
46    /// default tab.
47    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    /// Cycle to the previous tab, if we're at the first tab we return the last
54    /// tab.
55    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    /// Return tab's name as a styled `Line`
67    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}