Skip to content

Commit aa51d4e

Browse files
committed
refactor: organize ui components
1 parent 80d8ed8 commit aa51d4e

File tree

9 files changed

+120
-116
lines changed

9 files changed

+120
-116
lines changed

syndterm/src/application/mod.rs

+26-20
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,27 @@ use crate::{
99
auth::{
1010
self,
1111
device_flow::{DeviceAccessTokenResponse, DeviceAuthorizationResponse},
12-
Credential,
12+
AuthenticationProvider, Credential,
1313
},
1414
client::Client,
1515
command::Command,
1616
job::Jobs,
1717
terminal::Terminal,
1818
ui::{
1919
self,
20-
components::{authentication::Authentication, root::Root, tabs::Tab, Components},
20+
components::{authentication::AuthenticateState, root::Root, tabs::Tab, Components},
2121
theme::Theme,
2222
},
2323
};
2424

2525
mod direction;
2626
pub use direction::{Direction, IndexOutOfRange};
2727

28+
enum Screen {
29+
Login,
30+
Browse,
31+
}
32+
2833
pub struct Application {
2934
terminal: Terminal,
3035
client: Client,
@@ -33,6 +38,7 @@ pub struct Application {
3338
theme: Theme,
3439
idle_timer: Pin<Box<Sleep>>,
3540

41+
screen: Screen,
3642
should_render: bool,
3743
should_quit: bool,
3844
}
@@ -51,6 +57,7 @@ impl Application {
5157
jobs: Jobs::new(),
5258
theme: Theme::new(),
5359
idle_timer: Box::pin(tokio::time::sleep(Duration::from_millis(250))),
60+
screen: Screen::Login,
5461
should_quit: false,
5562
should_render: false,
5663
}
@@ -60,6 +67,7 @@ impl Application {
6067
self.client.set_credential(cred);
6168
self.components.auth.authenticated();
6269
self.initial_fetch();
70+
self.screen = Screen::Browse;
6371
self.should_render = true;
6472
}
6573

@@ -236,7 +244,8 @@ impl Application {
236244
let root = Root::new(&self.components, cx);
237245

238246
self.terminal
239-
.render(|frame| Widget::render(root, frame.size(), frame.buffer_mut()));
247+
.render(|frame| Widget::render(root, frame.size(), frame.buffer_mut()))
248+
.expect("Failed to render");
240249
}
241250

242251
#[allow(clippy::single_match)]
@@ -251,12 +260,13 @@ impl Application {
251260
}) => None,
252261
CrosstermEvent::Key(key) => {
253262
tracing::debug!("{key:?}");
254-
match self.state.screen {
263+
match self.screen {
255264
Screen::Login => match key.code {
256265
KeyCode::Enter => {
257-
if self.state.login.auth_state == AuthenticateState::NotAuthenticated {
266+
if self.components.auth.state() == &AuthenticateState::NotAuthenticated
267+
{
258268
return Some(Command::Authenticate(
259-
self.state.login.login_methods.selected_method(),
269+
self.components.auth.selected_provider(),
260270
));
261271
};
262272
}
@@ -267,7 +277,7 @@ impl Application {
267277
KeyCode::BackTab => {
268278
return Some(Command::MoveTabSelection(Direction::Left))
269279
}
270-
_ => match self.state.components.tabs.current() {
280+
_ => match self.components.tabs.current() {
271281
Tab::Feeds => match key.code {
272282
KeyCode::Char('j') => {
273283
return Some(Command::MoveEntry(Direction::Down))
@@ -333,7 +343,6 @@ impl Application {
333343
fn prompt_feed_unsubscription(&mut self) {
334344
// TODO: prompt deletion confirm
335345
let Some(url) = self
336-
.state
337346
.components
338347
.subscription
339348
.selected_feed_url()
@@ -370,20 +379,15 @@ impl Application {
370379

371380
impl Application {
372381
fn open_feed(&mut self) {
373-
let Some(feed_website_url) = self
374-
.state
375-
.components
376-
.subscription
377-
.selected_feed_website_url()
382+
let Some(feed_website_url) = self.components.subscription.selected_feed_website_url()
378383
else {
379384
return;
380385
};
381386
open::that(feed_website_url).ok();
382387
}
383388

384389
fn open_entry(&mut self) {
385-
let Some(entry_website_url) = self.state.components.entries.selected_entry_website_url()
386-
else {
390+
let Some(entry_website_url) = self.components.entries.selected_entry_website_url() else {
387391
return;
388392
};
389393
open::that(entry_website_url).ok();
@@ -407,9 +411,9 @@ impl Application {
407411
}
408412

409413
impl Application {
410-
fn authenticate(&mut self, method: AuthenticateMethod) {
411-
match method {
412-
AuthenticateMethod::Github => {
414+
fn authenticate(&mut self, provider: AuthenticationProvider) {
415+
match provider {
416+
AuthenticationProvider::Github => {
413417
let fut = async move {
414418
// TODO: error handling
415419
let res = auth::github::DeviceFlow::new()
@@ -426,7 +430,9 @@ impl Application {
426430
}
427431

428432
fn device_authorize_flow(&mut self, device_authorization: DeviceAuthorizationResponse) {
429-
self.state.login.auth_state = AuthenticateState::DeviceFlow(device_authorization.clone());
433+
self.components
434+
.auth
435+
.set_device_authorization_response(device_authorization.clone());
430436
self.should_render = true;
431437

432438
// attempt to open input screen in the browser
@@ -450,7 +456,7 @@ impl Application {
450456
}
451457

452458
fn complete_device_authroize_flow(&mut self, device_access_token: DeviceAccessTokenResponse) {
453-
let auth = Authentication::Github {
459+
let auth = Credential::Github {
454460
access_token: device_access_token.access_token,
455461
};
456462

syndterm/src/command.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use std::fmt::Display;
22

33
use crate::{
4-
application::{AuthenticateMethod, Direction},
5-
auth::device_flow::{DeviceAccessTokenResponse, DeviceAuthorizationResponse},
4+
application::Direction,
5+
auth::{
6+
device_flow::{DeviceAccessTokenResponse, DeviceAuthorizationResponse},
7+
AuthenticationProvider,
8+
},
69
client::{payload, query::subscription::SubscriptionOutput},
710
types::Feed,
811
};
@@ -13,7 +16,7 @@ pub enum Command {
1316
ResizeTerminal { columns: u16, rows: u16 },
1417
Idle,
1518

16-
Authenticate(AuthenticateMethod),
19+
Authenticate(AuthenticationProvider),
1720
DeviceAuthorizationFlow(DeviceAuthorizationResponse),
1821
CompleteDevieAuthorizationFlow(DeviceAccessTokenResponse),
1922

syndterm/src/ui/components/authentication.rs

+49-23
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
use ratatui::{
2-
prelude::{Buffer, Constraint, Layout, Rect},
2+
prelude::{Alignment, Buffer, Constraint, Layout, Rect},
33
style::{Modifier, Style},
44
text::{Line, Span, Text},
5-
widgets::{Block, Borders, List, ListItem, ListState, Paragraph, StatefulWidget, Widget},
5+
widgets::{
6+
Block, Borders, HighlightSpacing, List, ListItem, ListState, Paragraph, StatefulWidget,
7+
Widget,
8+
},
69
};
710

811
use crate::{
912
auth::{device_flow::DeviceAuthorizationResponse, AuthenticationProvider},
10-
ui::{extension::RectExt, Context},
13+
ui::{self, extension::RectExt, Context},
1114
};
1215

1316
/// Handle user authentication
@@ -35,46 +38,65 @@ impl Authentication {
3538
}
3639
}
3740

38-
pub fn authenticated(&mut self) {
39-
self.state = AuthenticateState::Authenticated;
41+
pub fn state(&self) -> &AuthenticateState {
42+
&self.state
4043
}
4144

4245
pub fn selected_provider(&self) -> AuthenticationProvider {
4346
self.providers[self.selected_provider_index]
4447
}
4548

49+
pub fn authenticated(&mut self) {
50+
self.state = AuthenticateState::Authenticated;
51+
}
52+
53+
pub fn set_device_authorization_response(&mut self, response: DeviceAuthorizationResponse) {
54+
self.state = AuthenticateState::DeviceFlow(response);
55+
}
56+
4657
pub(super) fn should_render(&self) -> bool {
47-
match self.state {
48-
AuthenticateState::NotAuthenticated | AuthenticateState::DeviceFlow(_) => true,
49-
_ => false,
50-
}
58+
matches!(
59+
self.state,
60+
AuthenticateState::NotAuthenticated | AuthenticateState::DeviceFlow(_)
61+
)
5162
}
5263
}
5364

5465
impl Authentication {
5566
pub(super) fn render(&self, area: Rect, buf: &mut Buffer, cx: &Context<'_>) {
5667
match self.state {
57-
AuthenticateState::NotAuthenticated => self.render_login(area, buf),
68+
AuthenticateState::NotAuthenticated => self.render_login(area, buf, cx),
5869
AuthenticateState::DeviceFlow(ref res) => self.render_device_flow(area, buf, cx, res),
5970
AuthenticateState::Authenticated => unreachable!(),
6071
}
6172
}
6273

63-
fn render_login(&self, area: Rect, buf: &mut Buffer) {
64-
let area = area.centered(50, 50);
74+
fn render_login(&self, area: Rect, buf: &mut Buffer, cx: &Context<'_>) {
75+
let area = area.centered(40, 50);
6576

6677
let vertical = Layout::vertical([Constraint::Length(2), Constraint::Min(1)]);
6778
let [title_area, methods_area] = area.split(&vertical);
6879

69-
let title = Paragraph::new(Text::styled("Login", Style::default()))
80+
let title = Paragraph::new(Span::styled("Login", cx.theme.login.title))
81+
.alignment(Alignment::Center)
7082
.block(Block::default().borders(Borders::BOTTOM));
7183

7284
let methods = {
73-
let items = vec![ListItem::new(Line::styled("with Github", Style::default()))];
74-
75-
List::new(items).highlight_symbol(">> ")
85+
let items = self
86+
.providers
87+
.iter()
88+
.map(|provider| match provider {
89+
AuthenticationProvider::Github => Text::from("with GitHub"),
90+
})
91+
.map(ListItem::new);
92+
93+
List::new(items)
94+
.highlight_symbol(ui::TABLE_HIGHLIGHT_SYMBOL)
95+
.highlight_style(cx.theme.login.selected_auth_provider_item)
96+
.highlight_spacing(HighlightSpacing::Always)
7697
};
77-
let mut methods_state = ListState::default().with_selected(Some(0));
98+
let mut methods_state =
99+
ListState::default().with_selected(Some(self.selected_provider_index));
78100

79101
Widget::render(title, title_area, buf);
80102
StatefulWidget::render(methods, methods_area, buf, &mut methods_state);
@@ -87,27 +109,31 @@ impl Authentication {
87109
cx: &Context<'_>,
88110
res: &DeviceAuthorizationResponse,
89111
) {
90-
let area = area.centered(50, 50);
112+
let area = area.centered(40, 50);
91113

92114
let vertical = Layout::vertical([Constraint::Length(2), Constraint::Min(1)]);
93115

94116
let [title_area, device_flow_area] = area.split(&vertical);
95117

96-
let title = Paragraph::new(Text::styled("Login", Style::default()))
118+
let title = Paragraph::new(Text::from("Login"))
119+
.alignment(Alignment::Center)
120+
.style(cx.theme.login.title)
97121
.block(Block::default().borders(Borders::BOTTOM));
98122

99123
let device_flow = Paragraph::new(vec![
124+
Line::from("Open the following URL and Enter the code"),
125+
Line::from(""),
100126
Line::from(vec![
101-
Span::styled("Code: ", Style::default()),
127+
Span::styled("URL: ", Style::default()),
102128
Span::styled(
103-
&res.user_code,
129+
res.verification_uri.to_string(),
104130
Style::default().add_modifier(Modifier::BOLD),
105131
),
106132
]),
107133
Line::from(vec![
108-
Span::styled("URL: ", Style::default()),
134+
Span::styled("Code: ", Style::default()),
109135
Span::styled(
110-
res.verification_uri.to_string(),
136+
&res.user_code,
111137
Style::default().add_modifier(Modifier::BOLD),
112138
),
113139
]),

syndterm/src/ui/components/prompt.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ impl Prompt {
2929
}
3030

3131
impl Prompt {
32-
pub fn render(&self, area: Rect, buf: &mut Buffer, cx: &Context<'_>) {
32+
pub fn render(&self, area: Rect, buf: &mut Buffer, cx: &Context<'_>, tab: Tab) {
3333
// If has error message, render it
3434
let paragraph = if let Some(error_message) = self.error_message.as_ref() {
3535
let line = Line::styled(error_message, cx.theme.error.message);
@@ -39,7 +39,7 @@ impl Prompt {
3939
.style(cx.theme.prompt.background)
4040
} else {
4141
let keys = [("q", "Quit"), ("Tab", "Next Tab"), ("j/k", "Up/Down")];
42-
let per_screen_keys = match cx.state.components.tabs.current() {
42+
let per_screen_keys = match tab {
4343
Tab::Subscription => [
4444
("a", "Subscribe"),
4545
("d", "Unsubscribe"),

syndterm/src/ui/components/root.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ impl<'a> Root<'a> {
3434
Tab::Feeds => self.components.entries.render(content_area, buf, cx),
3535
};
3636

37-
self.components.prompt.render(prompt_area, buf, cx);
37+
self.components
38+
.prompt
39+
.render(prompt_area, buf, cx, self.components.tabs.current());
3840
}
3941
}
4042

syndterm/src/ui/components/tabs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ impl Tabs {
5252
TuiTabs::new(self.tabs.clone())
5353
.style(cx.theme.tabs)
5454
.divider("")
55-
.select(cx.state.components.tabs.selected)
55+
.select(self.selected)
5656
.highlight_style(cx.theme.tabs_selected)
5757
.render(tabs, buf);
5858
}

0 commit comments

Comments
 (0)