notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

nav.rs (6368B)


      1 use egui_nav::{NavAction, NavResponse};
      2 use enostr::Pubkey;
      3 use hashbrown::HashSet;
      4 use notedeck::{AppAction, AppContext, ReplacementType, Router};
      5 
      6 use crate::{
      7     cache::{
      8         ConversationCache, ConversationId, ConversationIdentifierUnowned, ParticipantSetUnowned,
      9     },
     10     loader::MessagesLoader,
     11     nip17::send_conversation_message,
     12     open_conversation_with_prefetch,
     13 };
     14 
     15 #[derive(Clone, Debug)]
     16 pub enum Route {
     17     ConvoList,
     18     CreateConvo,
     19     Conversation,
     20 }
     21 
     22 #[derive(Debug)]
     23 pub enum MessagesAction {
     24     SendMessage {
     25         conversation_id: ConversationId,
     26         content: String,
     27     },
     28     Open(ConversationId),
     29     Creating,
     30     Back,
     31     Create {
     32         recipient: Pubkey,
     33     },
     34     ToggleChrome,
     35 }
     36 
     37 pub struct MessagesUiResponse {
     38     pub nav_response: Option<NavResponse<Option<MessagesAction>>>,
     39     pub conversation_panel_response: Option<MessagesAction>,
     40 }
     41 
     42 /// Apply UI responses and navigation actions to the messages router.
     43 pub fn process_messages_ui_response(
     44     resp: MessagesUiResponse,
     45     ctx: &mut AppContext,
     46     cache: &mut ConversationCache,
     47     router: &mut Router<Route>,
     48     is_narrow: bool,
     49     loader: &MessagesLoader,
     50     inflight_messages: &mut HashSet<ConversationId>,
     51 ) -> Option<AppAction> {
     52     let mut action = None;
     53     if let Some(convo_resp) = resp.conversation_panel_response {
     54         action = handle_messages_action(
     55             convo_resp,
     56             ctx,
     57             cache,
     58             router,
     59             is_narrow,
     60             loader,
     61             inflight_messages,
     62         );
     63     }
     64 
     65     let Some(nav) = resp.nav_response else {
     66         return action;
     67     };
     68 
     69     action.or(process_nav_resp(
     70         nav,
     71         ctx,
     72         cache,
     73         router,
     74         is_narrow,
     75         loader,
     76         inflight_messages,
     77     ))
     78 }
     79 
     80 /// Handle navigation responses emitted by the messages UI.
     81 fn process_nav_resp(
     82     nav: NavResponse<Option<MessagesAction>>,
     83     ctx: &mut AppContext,
     84     cache: &mut ConversationCache,
     85     router: &mut Router<Route>,
     86     is_narrow: bool,
     87     loader: &MessagesLoader,
     88     inflight_messages: &mut HashSet<ConversationId>,
     89 ) -> Option<AppAction> {
     90     let mut app_action = None;
     91     if let Some(action) = nav.response.or(nav.title_response) {
     92         app_action = handle_messages_action(
     93             action,
     94             ctx,
     95             cache,
     96             router,
     97             is_narrow,
     98             loader,
     99             inflight_messages,
    100         );
    101     }
    102 
    103     let Some(action) = nav.action else {
    104         return app_action;
    105     };
    106 
    107     match action {
    108         NavAction::Returning(_) => {}
    109         NavAction::Resetting => {}
    110         NavAction::Dragging => {}
    111         NavAction::Returned(_) => {
    112             router.pop();
    113             if is_narrow {
    114                 cache.active = None;
    115             }
    116         }
    117         NavAction::Navigating => {}
    118         NavAction::Navigated => {
    119             router.navigating = false;
    120             if router.is_replacing() {
    121                 router.complete_replacement();
    122             }
    123         }
    124     }
    125 
    126     app_action
    127 }
    128 
    129 /// Execute a messages action and return an optional app action.
    130 fn handle_messages_action(
    131     action: MessagesAction,
    132     ctx: &mut AppContext<'_>,
    133     cache: &mut ConversationCache,
    134     router: &mut Router<Route>,
    135     is_narrow: bool,
    136     loader: &MessagesLoader,
    137     inflight_messages: &mut HashSet<ConversationId>,
    138 ) -> Option<AppAction> {
    139     let mut app_action = None;
    140     match action {
    141         MessagesAction::SendMessage {
    142             conversation_id,
    143             content,
    144         } => send_conversation_message(conversation_id, content, cache, ctx),
    145         MessagesAction::Open(conversation_id) => open_coversation_action(
    146             conversation_id,
    147             ctx,
    148             cache,
    149             router,
    150             is_narrow,
    151             loader,
    152             inflight_messages,
    153         ),
    154         MessagesAction::Create { recipient } => {
    155             let selected = ctx.accounts.selected_account_pubkey();
    156             let participants = vec![recipient.bytes(), selected.bytes()];
    157             let id = cache
    158                 .registry
    159                 .get_or_insert(ConversationIdentifierUnowned::Nip17(
    160                     ParticipantSetUnowned::new(participants),
    161                 ));
    162 
    163             cache.initialize_conversation(id, vec![recipient, *selected]);
    164             open_conversation_with_prefetch(&mut ctx.remote, ctx.accounts, cache, id);
    165             request_conversation_messages(
    166                 cache,
    167                 ctx.accounts.selected_account_pubkey(),
    168                 id,
    169                 loader,
    170                 inflight_messages,
    171             );
    172 
    173             if is_narrow {
    174                 router.route_to_replaced(Route::Conversation, ReplacementType::Single);
    175             } else {
    176                 router.go_back();
    177             }
    178         }
    179         MessagesAction::Creating => {
    180             router.route_to(Route::CreateConvo);
    181         }
    182         MessagesAction::Back => {
    183             router.go_back();
    184         }
    185         MessagesAction::ToggleChrome => app_action = Some(AppAction::ToggleChrome),
    186     }
    187 
    188     app_action
    189 }
    190 
    191 /// Activate a conversation and request its message history.
    192 fn open_coversation_action(
    193     id: ConversationId,
    194     ctx: &mut AppContext<'_>,
    195     cache: &mut ConversationCache,
    196     router: &mut Router<Route>,
    197     is_narrow: bool,
    198     loader: &MessagesLoader,
    199     inflight_messages: &mut HashSet<ConversationId>,
    200 ) {
    201     open_conversation_with_prefetch(&mut ctx.remote, ctx.accounts, cache, id);
    202     request_conversation_messages(
    203         cache,
    204         ctx.accounts.selected_account_pubkey(),
    205         id,
    206         loader,
    207         inflight_messages,
    208     );
    209 
    210     if is_narrow {
    211         router.route_to(Route::Conversation);
    212     }
    213 }
    214 
    215 /// Schedule a background load for a conversation if it is not already in flight.
    216 fn request_conversation_messages(
    217     cache: &ConversationCache,
    218     me: &Pubkey,
    219     conversation_id: ConversationId,
    220     loader: &MessagesLoader,
    221     inflight_messages: &mut HashSet<ConversationId>,
    222 ) {
    223     if inflight_messages.contains(&conversation_id) {
    224         return;
    225     }
    226 
    227     let Some(conversation) = cache.get(conversation_id) else {
    228         return;
    229     };
    230 
    231     inflight_messages.insert(conversation_id);
    232     loader.load_conversation_messages(
    233         conversation_id,
    234         conversation.metadata.participants.clone(),
    235         *me,
    236     );
    237 }