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 }