Skip to content

Commit

Permalink
feat(term): add unsubscribe popup
Browse files Browse the repository at this point in the history
  • Loading branch information
ymgyt committed May 5, 2024
1 parent 4810372 commit d7db514
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 20 deletions.
43 changes: 26 additions & 17 deletions crates/synd_term/src/application/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use crate::{
ui::{
self,
components::{
authentication::AuthenticateState, filter::FeedFilter, root::Root, tabs::Tab,
Components,
authentication::AuthenticateState, filter::FeedFilter, root::Root,
subscription::UnsubscribeSelection, tabs::Tab, Components,
},
theme::Theme,
},
Expand Down Expand Up @@ -379,7 +379,30 @@ impl Application {
self.should_render();
}
Command::PromptFeedUnsubscription => {
self.prompt_feed_unsubscription();
if self.components.subscription.selected_feed().is_some() {
self.components.subscription.show_unsubscribe_popup(true);
self.keymaps().enable(KeymapId::UnsubscribePopupSelection);
self.should_render();
}
}
Command::MoveFeedUnsubscriptionPopupSelection(direction) => {
self.components
.subscription
.move_unsubscribe_popup_selection(direction);
self.should_render();
}
Command::SelectFeedUnsubscriptionPopup => {
if let (UnsubscribeSelection::Yes, Some(feed)) =
self.components.subscription.unsubscribe_popup_selection()
{
self.unsubscribe_feed(feed.url.clone());
}
next = Some(Command::CancelFeedUnsubscriptionPopup);
self.should_render();
}
Command::CancelFeedUnsubscriptionPopup => {
self.components.subscription.show_unsubscribe_popup(false);
self.keymaps().disable(KeymapId::UnsubscribePopupSelection);
self.should_render();
}
Command::SubscribeFeed { input } => {
Expand Down Expand Up @@ -696,20 +719,6 @@ impl Application {
self.jobs.futures.push(fut);
}

fn prompt_feed_unsubscription(&mut self) {
// TODO: prompt deletion confirm
let Some(url) = self
.components
.subscription
.selected_feed()
.map(|feed| feed.url.clone())
else {
return;
};
let fut = async move { Ok(Command::UnsubscribeFeed { url }) }.boxed();
self.jobs.futures.push(fut);
}

fn subscribe_feed(&mut self, input: SubscribeFeedInput) {
let client = self.client.clone();
let request_seq = self.in_flight.add(RequestId::SubscribeFeed);
Expand Down
15 changes: 15 additions & 0 deletions crates/synd_term/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub enum Command {
PromptFeedSubscription,
PromptFeedEdition,
PromptFeedUnsubscription,
MoveFeedUnsubscriptionPopupSelection(Direction),
SelectFeedUnsubscriptionPopup,
CancelFeedUnsubscriptionPopup,
SubscribeFeed {
input: SubscribeFeedInput,
},
Expand Down Expand Up @@ -167,6 +170,18 @@ impl Command {
pub fn prompt_feed_unsubscription() -> Self {
Command::PromptFeedUnsubscription
}
pub fn move_feed_unsubscription_popup_selection_left() -> Self {
Command::MoveFeedUnsubscriptionPopupSelection(Direction::Left)
}
pub fn move_feed_unsubscription_popup_selection_right() -> Self {
Command::MoveFeedUnsubscriptionPopupSelection(Direction::Right)
}
pub fn select_feed_unsubscription_popup() -> Self {
Command::SelectFeedUnsubscriptionPopup
}
pub fn cancel_feed_unsubscription_popup() -> Self {
Command::CancelFeedUnsubscriptionPopup
}
pub fn move_up_subscribed_feed() -> Self {
Command::MoveSubscribedFeed(Direction::Up)
}
Expand Down
7 changes: 7 additions & 0 deletions crates/synd_term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ pub fn default() -> KeymapsConfig {
"/" => activate_search_filtering,
"esc" => deactivate_filtering,
});
let unsubscribe_popup = keymap!({
"h" | "left" => move_feed_unsubscription_popup_selection_left,
"l" | "right" => move_feed_unsubscription_popup_selection_right,
"enter" => select_feed_unsubscription_popup,
"esc" => cancel_feed_unsubscription_popup,
});
let global = keymap!({
"q" | "C-c" => quit ,
});
Expand All @@ -50,6 +56,7 @@ pub fn default() -> KeymapsConfig {
entries,
subscription,
filter,
unsubscribe_popup,
global,
}
}
6 changes: 6 additions & 0 deletions crates/synd_term/src/keymap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum KeymapId {
Subscription = 4,
Filter = 5,
CategoryFiltering = 6,
UnsubscribePopupSelection = 7,
}

#[derive(Debug)]
Expand Down Expand Up @@ -76,6 +77,7 @@ pub struct KeymapsConfig {
pub entries: KeyTrie,
pub subscription: KeyTrie,
pub filter: KeyTrie,
pub unsubscribe_popup: KeyTrie,
pub global: KeyTrie,
}

Expand All @@ -101,6 +103,10 @@ impl Keymaps {
Keymap::new(KeymapId::Subscription, config.subscription),
Keymap::new(KeymapId::Filter, config.filter),
Keymap::new(KeymapId::CategoryFiltering, KeyTrie::default()),
Keymap::new(
KeymapId::UnsubscribePopupSelection,
config.unsubscribe_popup,
),
];

Self { keymaps }
Expand Down
138 changes: 136 additions & 2 deletions crates/synd_term/src/ui/components/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use ratatui::{
text::{Line, Span},
widgets::{
block::{Position, Title},
Block, BorderType, Borders, Cell, HighlightSpacing, Padding, Row, Scrollbar,
ScrollbarOrientation, ScrollbarState, StatefulWidget, Table, TableState, Widget,
Block, BorderType, Borders, Cell, HighlightSpacing, Padding, Paragraph, Row, Scrollbar,
ScrollbarOrientation, ScrollbarState, StatefulWidget, Table, TableState, Tabs, Widget,
},
};
use synd_feed::types::{FeedType, FeedUrl};
Expand All @@ -20,6 +20,7 @@ use crate::{
ui::{
self,
components::filter::{FeedFilter, FilterResult},
extension::RectExt,
Context,
},
};
Expand All @@ -29,6 +30,28 @@ pub struct Subscription {
feeds: Vec<types::Feed>,
effective_feeds: Vec<usize>,
filter: FeedFilter,

unsubscribe_popup: UnsubscribePopup,
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum UnsubscribeSelection {
Yes,
No,
}

impl UnsubscribeSelection {
fn toggle(self) -> Self {
match self {
UnsubscribeSelection::Yes => UnsubscribeSelection::No,
UnsubscribeSelection::No => UnsubscribeSelection::Yes,
}
}
}

struct UnsubscribePopup {
selection: UnsubscribeSelection,
selected_feed: Option<types::Feed>,
}

impl Subscription {
Expand All @@ -38,6 +61,10 @@ impl Subscription {
feeds: Vec::new(),
effective_feeds: Vec::new(),
filter: FeedFilter::default(),
unsubscribe_popup: UnsubscribePopup {
selection: UnsubscribeSelection::Yes,
selected_feed: None,
},
}
}

Expand All @@ -55,6 +82,21 @@ impl Subscription {
.map(|&idx| self.feeds.get(idx).unwrap())
}

pub fn show_unsubscribe_popup(&mut self, show: bool) {
if show {
self.unsubscribe_popup.selected_feed = self.selected_feed().cloned();
} else {
self.unsubscribe_popup.selected_feed = None;
}
}

pub fn unsubscribe_popup_selection(&self) -> (UnsubscribeSelection, Option<&types::Feed>) {
(
self.unsubscribe_popup.selection,
self.unsubscribe_popup.selected_feed.as_ref(),
)
}

pub fn update_subscription(&mut self, populate: Populate, subscription: SubscriptionOutput) {
let feed_metas = subscription.feeds.nodes.into_iter().map(types::Feed::from);
match populate {
Expand Down Expand Up @@ -110,6 +152,12 @@ impl Subscription {
self.selected_feed_index = self.effective_feeds.len() - 1;
}
}

pub fn move_unsubscribe_popup_selection(&mut self, direction: Direction) {
if matches!(direction, Direction::Left | Direction::Right) {
self.unsubscribe_popup.selection = self.unsubscribe_popup.selection.toggle();
}
}
}

impl Subscription {
Expand All @@ -119,6 +167,10 @@ impl Subscription {

self.render_feeds(feeds_area, buf, cx);
self.render_feed_detail(feed_detail_area, buf, cx);

if let Some(feed) = self.unsubscribe_popup.selected_feed.as_ref() {
self.render_unsubscribe_popup(area, buf, cx, feed);
}
}

fn render_feeds(&self, area: Rect, buf: &mut Buffer, cx: &Context<'_>) {
Expand Down Expand Up @@ -375,4 +427,86 @@ impl Subscription {

Widget::render(table, entries_area, buf);
}

fn render_unsubscribe_popup(
&self,
area: Rect,
buf: &mut Buffer,
cx: &Context<'_>,
feed: &types::Feed,
) {
let area = {
let area = area.centered(60, 60);
let vertical = Layout::vertical([
Constraint::Fill(1),
Constraint::Min(12),
Constraint::Fill(2),
]);
let [_, area, _] = vertical.areas(area);
area.reset(buf);
area
};

let block = Block::new()
.title_top("Unsubscribe")
.title_alignment(Alignment::Center)
.title_style(Style::new().add_modifier(Modifier::BOLD))
.padding(Padding {
left: 1,
right: 1,
top: 1,
bottom: 1,
})
.borders(Borders::ALL)
.style(cx.theme.background);

let inner_area = block.inner(area);
let vertical = Layout::vertical([Constraint::Length(6), Constraint::Fill(1)]);
let [info_area, selection_area] = vertical.areas(inner_area);

block.render(area, buf);

// for align line
let feed_n = "Feed: ".len() + feed.title.as_deref().unwrap_or("-").len();
let url_n = "URL : ".len() + feed.url.as_str().len();

Paragraph::new(vec![
Line::from("Do you unsubscribe from this feed?"),
Line::from(""),
Line::from(vec![
Span::from("Feed: "),
Span::from(feed.title.as_deref().unwrap_or("-")).bold(),
Span::from(" ".repeat(url_n.saturating_sub(feed_n))),
]),
Line::from(vec![
Span::from("URL : "),
Span::from(feed.url.to_string()).bold(),
Span::from(" ".repeat(feed_n.saturating_sub(url_n))),
]),
])
.alignment(Alignment::Center)
.block(
Block::new()
.borders(Borders::BOTTOM)
.border_type(BorderType::Plain)
.border_style(Style::new().add_modifier(Modifier::DIM)),
)
.render(info_area, buf);

// align center
let horizontal =
Layout::horizontal([Constraint::Fill(1), Constraint::Min(1), Constraint::Fill(1)]);
let [_, selection_area, _] = horizontal.areas(selection_area);

Tabs::new([" Yes ", " No "])
.style(cx.theme.tabs)
.divider("")
.padding(" ", " ")
.select(match self.unsubscribe_popup.selection {
UnsubscribeSelection::Yes => 0,
UnsubscribeSelection::No => 1,
})
.highlight_style(cx.theme.selection_popup.highlight)
.render(selection_area, buf);
}
}
16 changes: 15 additions & 1 deletion crates/synd_term/src/ui/extension.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use ratatui::prelude::{Constraint, Direction, Layout, Rect};
use ratatui::{
buffer::Buffer,
prelude::{Constraint, Direction, Layout, Rect},
};

pub(super) trait RectExt {
/// Create centered Rect
fn centered(self, percent_x: u16, percent_y: u16) -> Rect;

/// Reset this area
fn reset(&self, buf: &mut Buffer);
}

impl RectExt for Rect {
Expand All @@ -27,4 +33,12 @@ impl RectExt for Rect {
])
.split(layout[1])[1]
}

fn reset(&self, buf: &mut Buffer) {
for x in self.x..(self.x + self.width) {
for y in self.y..(self.y + self.height) {
buf.get_mut(x, y).reset();
}
}
}
}
9 changes: 9 additions & 0 deletions crates/synd_term/src/ui/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct Theme {
pub error: ErrorTheme,
pub default_icon_fg: Color,
pub requirement: RequirementLabelTheme,
pub selection_popup: SelectionPopup,
}

#[derive(Clone)]
Expand Down Expand Up @@ -58,6 +59,11 @@ pub struct RequirementLabelTheme {
pub fg: Color,
}

#[derive(Clone)]
pub struct SelectionPopup {
pub highlight: Style,
}

impl Theme {
pub fn with_palette(p: &Palette) -> Self {
let gray = tailwind::ZINC;
Expand Down Expand Up @@ -101,6 +107,9 @@ impl Theme {
may: Color::Rgb(35, 57, 91),
fg: bg,
},
selection_popup: SelectionPopup {
highlight: Style::new().bg(Color::Yellow).fg(bg),
},
}
}
pub fn new() -> Self {
Expand Down

0 comments on commit d7db514

Please sign in to comment.