synd_term/
command.rs

1use std::{fmt::Display, sync::Arc};
2use synd_auth::device_flow::DeviceAuthorizationResponse;
3use synd_feed::types::{Category, FeedUrl};
4
5use crate::{
6    application::{Direction, Populate, RequestSequence},
7    auth::{AuthenticationProvider, Credential, Verified},
8    client::{
9        github::{FetchNotificationsParams, GithubError},
10        synd_api::{
11            mutation::subscribe_feed::SubscribeFeedInput, payload,
12            query::subscription::SubscriptionOutput, SyndApiError,
13        },
14    },
15    types::{
16        github::{
17            IssueContext, IssueOrPullRequest, Notification, NotificationId, PullRequestContext,
18            PullRequestState, Reason,
19        },
20        Feed,
21    },
22    ui::components::{filter::FilterLane, gh_notifications::GhNotificationFilterUpdater},
23};
24
25#[derive(Debug, Clone)]
26pub(crate) enum ApiResponse {
27    DeviceFlowAuthorization {
28        provider: AuthenticationProvider,
29        device_authorization: DeviceAuthorizationResponse,
30    },
31    DeviceFlowCredential {
32        credential: Verified<Credential>,
33    },
34    SubscribeFeed {
35        feed: Box<Feed>,
36    },
37    UnsubscribeFeed {
38        url: FeedUrl,
39    },
40    FetchSubscription {
41        populate: Populate,
42        subscription: SubscriptionOutput,
43    },
44    FetchEntries {
45        populate: Populate,
46        payload: payload::FetchEntriesPayload,
47    },
48    FetchGithubNotifications {
49        populate: Populate,
50        notifications: Vec<Notification>,
51    },
52    FetchGithubIssue {
53        notification_id: NotificationId,
54        issue: IssueContext,
55    },
56    FetchGithubPullRequest {
57        notification_id: NotificationId,
58        pull_request: PullRequestContext,
59    },
60    MarkGithubNotificationAsDone {
61        notification_id: NotificationId,
62    },
63    UnsubscribeGithubThread {},
64}
65
66impl Display for ApiResponse {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        match self {
69            ApiResponse::DeviceFlowCredential { .. } => f.write_str("DeviceFlowCredential"),
70            ApiResponse::FetchSubscription { .. } => f.write_str("FetchSubscription"),
71            ApiResponse::FetchEntries { .. } => f.write_str("FetchEntries"),
72            ApiResponse::FetchGithubNotifications { .. } => f.write_str("FetchGithubNotifications"),
73            ApiResponse::FetchGithubIssue { .. } => f.write_str("FetchGithubIssue"),
74            ApiResponse::FetchGithubPullRequest { .. } => f.write_str("FetchGithubPullRequest"),
75            cmd => write!(f, "{cmd:?}"),
76        }
77    }
78}
79
80#[derive(Debug, Clone)]
81pub(crate) enum Command {
82    Nop,
83    Quit,
84    ResizeTerminal {
85        _columns: u16,
86        _rows: u16,
87    },
88    RenderThrobber,
89    Idle,
90
91    Authenticate,
92    MoveAuthenticationProvider(Direction),
93
94    HandleApiResponse {
95        request_seq: RequestSequence,
96        response: ApiResponse,
97    },
98
99    RefreshCredential {
100        credential: Verified<Credential>,
101    },
102
103    MoveTabSelection(Direction),
104
105    // Subscription
106    MoveSubscribedFeed(Direction),
107    MoveSubscribedFeedFirst,
108    MoveSubscribedFeedLast,
109    PromptFeedSubscription,
110    PromptFeedEdition,
111    PromptFeedUnsubscription,
112    MoveFeedUnsubscriptionPopupSelection(Direction),
113    SelectFeedUnsubscriptionPopup,
114    CancelFeedUnsubscriptionPopup,
115    SubscribeFeed {
116        input: SubscribeFeedInput,
117    },
118    FetchSubscription {
119        after: Option<String>,
120        first: i64,
121    },
122    ReloadSubscription,
123    OpenFeed,
124
125    // Entries
126    FetchEntries {
127        after: Option<String>,
128        first: i64,
129    },
130    ReloadEntries,
131    MoveEntry(Direction),
132    MoveEntryFirst,
133    MoveEntryLast,
134    OpenEntry,
135    BrowseEntry,
136
137    // Filter
138    MoveFilterRequirement(Direction),
139    ActivateCategoryFilterling,
140    ActivateSearchFiltering,
141    PromptChanged,
142    DeactivateFiltering,
143    ToggleFilterCategory {
144        lane: FilterLane,
145        category: Category<'static>,
146    },
147    ActivateAllFilterCategories {
148        lane: FilterLane,
149    },
150    DeactivateAllFilterCategories {
151        lane: FilterLane,
152    },
153
154    // Theme
155    RotateTheme,
156
157    // Latest release check
158    InformLatestRelease(update_informer::Version),
159
160    // Github notifications
161    FetchGhNotifications {
162        populate: Populate,
163        params: FetchNotificationsParams,
164    },
165    FetchGhNotificationDetails {
166        contexts: Vec<IssueOrPullRequest>,
167    },
168    MoveGhNotification(Direction),
169    MoveGhNotificationFirst,
170    MoveGhNotificationLast,
171    OpenGhNotification {
172        with_mark_as_done: bool,
173    },
174    ReloadGhNotifications,
175    MarkGhNotificationAsDone {
176        all: bool,
177    },
178    UnsubscribeGhThread,
179    OpenGhNotificationFilterPopup,
180    CloseGhNotificationFilterPopup,
181    UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater),
182
183    // Error
184    HandleError {
185        message: String,
186    },
187    HandleApiError {
188        // use Arc for impl Clone
189        error: Arc<SyndApiError>,
190        request_seq: RequestSequence,
191    },
192    HandleOauthApiError {
193        // use Arc for impl Clone
194        error: Arc<anyhow::Error>,
195        request_seq: RequestSequence,
196    },
197    HandleGithubApiError {
198        // use Arc for impl Clone
199        error: Arc<GithubError>,
200        request_seq: RequestSequence,
201    },
202}
203
204impl Display for Command {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        match self {
207            Command::HandleApiResponse { response, .. } => response.fmt(f),
208            Command::FetchGhNotificationDetails { .. } => f
209                .debug_struct("FetchGhNotificationDetails")
210                .finish_non_exhaustive(),
211            _ => write!(f, "{self:?}"),
212        }
213    }
214}
215
216impl Command {
217    pub(crate) fn api_error(error: SyndApiError, request_seq: RequestSequence) -> Self {
218        Command::HandleApiError {
219            error: Arc::new(error),
220            request_seq,
221        }
222    }
223    pub(crate) fn oauth_api_error(error: anyhow::Error, request_seq: RequestSequence) -> Self {
224        Command::HandleOauthApiError {
225            error: Arc::new(error),
226            request_seq,
227        }
228    }
229}
230
231impl Command {
232    pub fn quit() -> Self {
233        Command::Quit
234    }
235    pub fn authenticate() -> Self {
236        Command::Authenticate
237    }
238    pub fn move_right_tab_selection() -> Self {
239        Command::MoveTabSelection(Direction::Right)
240    }
241    pub fn move_left_tab_selection() -> Self {
242        Command::MoveTabSelection(Direction::Left)
243    }
244    pub fn move_up_authentication_provider() -> Self {
245        Command::MoveAuthenticationProvider(Direction::Up)
246    }
247    pub fn move_down_authentication_provider() -> Self {
248        Command::MoveAuthenticationProvider(Direction::Down)
249    }
250    pub fn move_up_entry() -> Self {
251        Command::MoveEntry(Direction::Up)
252    }
253    pub fn move_down_entry() -> Self {
254        Command::MoveEntry(Direction::Down)
255    }
256    pub fn reload_entries() -> Self {
257        Command::ReloadEntries
258    }
259    pub fn open_entry() -> Self {
260        Command::OpenEntry
261    }
262    pub fn browse_entry() -> Self {
263        Command::BrowseEntry
264    }
265    pub fn move_entry_first() -> Self {
266        Command::MoveEntryFirst
267    }
268    pub fn move_entry_last() -> Self {
269        Command::MoveEntryLast
270    }
271    pub fn prompt_feed_subscription() -> Self {
272        Command::PromptFeedSubscription
273    }
274    pub fn prompt_feed_edition() -> Self {
275        Command::PromptFeedEdition
276    }
277    pub fn prompt_feed_unsubscription() -> Self {
278        Command::PromptFeedUnsubscription
279    }
280    pub fn move_feed_unsubscription_popup_selection_left() -> Self {
281        Command::MoveFeedUnsubscriptionPopupSelection(Direction::Left)
282    }
283    pub fn move_feed_unsubscription_popup_selection_right() -> Self {
284        Command::MoveFeedUnsubscriptionPopupSelection(Direction::Right)
285    }
286    pub fn select_feed_unsubscription_popup() -> Self {
287        Command::SelectFeedUnsubscriptionPopup
288    }
289    pub fn cancel_feed_unsubscription_popup() -> Self {
290        Command::CancelFeedUnsubscriptionPopup
291    }
292    pub fn move_up_subscribed_feed() -> Self {
293        Command::MoveSubscribedFeed(Direction::Up)
294    }
295    pub fn move_down_subscribed_feed() -> Self {
296        Command::MoveSubscribedFeed(Direction::Down)
297    }
298    pub fn reload_subscription() -> Self {
299        Command::ReloadSubscription
300    }
301    pub fn open_feed() -> Self {
302        Command::OpenFeed
303    }
304    pub fn move_subscribed_feed_first() -> Self {
305        Command::MoveSubscribedFeedFirst
306    }
307    pub fn move_subscribed_feed_last() -> Self {
308        Command::MoveSubscribedFeedLast
309    }
310    pub fn move_filter_requirement_left() -> Self {
311        Command::MoveFilterRequirement(Direction::Left)
312    }
313    pub fn move_filter_requirement_right() -> Self {
314        Command::MoveFilterRequirement(Direction::Right)
315    }
316    pub fn activate_category_filtering() -> Self {
317        Command::ActivateCategoryFilterling
318    }
319    pub fn activate_search_filtering() -> Self {
320        Command::ActivateSearchFiltering
321    }
322    pub fn deactivate_filtering() -> Self {
323        Command::DeactivateFiltering
324    }
325    pub fn rotate_theme() -> Self {
326        Command::RotateTheme
327    }
328    pub fn move_up_gh_notification() -> Self {
329        Command::MoveGhNotification(Direction::Up)
330    }
331    pub fn move_down_gh_notification() -> Self {
332        Command::MoveGhNotification(Direction::Down)
333    }
334    pub fn move_gh_notification_first() -> Self {
335        Command::MoveGhNotificationFirst
336    }
337    pub fn move_gh_notification_last() -> Self {
338        Command::MoveGhNotificationLast
339    }
340    pub fn open_gh_notification() -> Self {
341        Command::OpenGhNotification {
342            with_mark_as_done: false,
343        }
344    }
345    pub fn open_gh_notification_with_done() -> Self {
346        Command::OpenGhNotification {
347            with_mark_as_done: true,
348        }
349    }
350    pub fn reload_gh_notifications() -> Self {
351        Command::ReloadGhNotifications
352    }
353    pub fn mark_gh_notification_as_done() -> Self {
354        Command::MarkGhNotificationAsDone { all: false }
355    }
356    pub fn mark_gh_notification_as_done_all() -> Self {
357        Command::MarkGhNotificationAsDone { all: true }
358    }
359    pub fn unsubscribe_gh_thread() -> Self {
360        Command::UnsubscribeGhThread
361    }
362    pub fn open_gh_notification_filter_popup() -> Self {
363        Command::OpenGhNotificationFilterPopup
364    }
365    pub fn close_gh_notification_filter_popup() -> Self {
366        Command::CloseGhNotificationFilterPopup
367    }
368    pub fn toggle_gh_notification_filter_popup_include_unread() -> Self {
369        Command::UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater {
370            toggle_include: true,
371            ..Default::default()
372        })
373    }
374    pub fn toggle_gh_notification_filter_popup_participating() -> Self {
375        Command::UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater {
376            toggle_participating: true,
377            ..Default::default()
378        })
379    }
380    pub fn toggle_gh_notification_filter_popup_visibility_public() -> Self {
381        Command::UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater {
382            toggle_visilibty_public: true,
383            ..Default::default()
384        })
385    }
386    pub fn toggle_gh_notification_filter_popup_visibility_private() -> Self {
387        Command::UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater {
388            toggle_visilibty_private: true,
389            ..Default::default()
390        })
391    }
392    pub fn toggle_gh_notification_filter_popup_pr_open() -> Self {
393        Command::UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater {
394            toggle_pull_request_condition: Some(PullRequestState::Open),
395            ..Default::default()
396        })
397    }
398    pub fn toggle_gh_notification_filter_popup_pr_closed() -> Self {
399        Command::UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater {
400            toggle_pull_request_condition: Some(PullRequestState::Closed),
401            ..Default::default()
402        })
403    }
404    pub fn toggle_gh_notification_filter_popup_pr_merged() -> Self {
405        Command::UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater {
406            toggle_pull_request_condition: Some(PullRequestState::Merged),
407            ..Default::default()
408        })
409    }
410    pub fn toggle_gh_notification_filter_popup_reason_mentioned() -> Self {
411        Command::UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater {
412            toggle_reason: Some(Reason::Mention),
413            ..Default::default()
414        })
415    }
416    pub fn toggle_gh_notification_filter_popup_reason_review() -> Self {
417        Command::UpdateGhnotificationFilterPopupOptions(GhNotificationFilterUpdater {
418            toggle_reason: Some(Reason::ReviewRequested),
419            ..Default::default()
420        })
421    }
422}