corrections sur les indicateurs
1c88366 parent: b199364 modified
src/config/feed.rs +12 -0 | @@ -26,6 +26,12 @@ pub struct Feed { | ||
| 26 | 26 | pub enabled: bool, |
| 27 | 27 | /// Tags for categorizing the feed (e.g., "Tech", "News", "French") |
| 28 | 28 | pub tags: Vec<String>, |
| 29 | + /// Number of episodes downloaded for this feed | |
| 30 | + pub downloaded_episodes_count: usize, | |
| 31 | + /// Name of the last downloaded episode | |
| 32 | + pub last_downloaded_episode_name: Option<String>, | |
| 33 | + /// Download date of the last downloaded episode | |
| 34 | + pub last_downloaded_episode_date: Option<DateTime<Utc>>, | |
| 29 | 35 | } |
| 30 | 36 | |
| 31 | 37 | impl Default for Feed { |
| @@ -39,6 +45,9 @@ impl Default for Feed { | ||
| 39 | 45 | last_episode_date: None, |
| 40 | 46 | enabled: true, |
| 41 | 47 | tags: Vec::new(), |
| 48 | + downloaded_episodes_count: 0, | |
| 49 | + last_downloaded_episode_name: None, | |
| 50 | + last_downloaded_episode_date: None, | |
| 42 | 51 | } |
| 43 | 52 | } |
| 44 | 53 | } |
| @@ -55,6 +64,9 @@ impl Feed { | ||
| 55 | 64 | last_episode_date: None, |
| 56 | 65 | enabled: true, |
| 57 | 66 | tags: Vec::new(), |
| 67 | + downloaded_episodes_count: 0, | |
| 68 | + last_downloaded_episode_name: None, | |
| 69 | + last_downloaded_episode_date: None, | |
| 58 | 70 | } |
| 59 | 71 | } |
| 60 | 72 | |
| @@ -26,6 +26,12 @@ pub struct Feed { | |||
| 26 | pub enabled: bool, | 26 | pub enabled: bool, |
| 27 | /// Tags for categorizing the feed (e.g., "Tech", "News", "French") | 27 | /// Tags for categorizing the feed (e.g., "Tech", "News", "French") |
| 28 | pub tags: Vec<String>, | 28 | pub tags: Vec<String>, |
| 29 | + /// Number of episodes downloaded for this feed | ||
| 30 | + pub downloaded_episodes_count: usize, | ||
| 31 | + /// Name of the last downloaded episode | ||
| 32 | + pub last_downloaded_episode_name: Option<String>, | ||
| 33 | + /// Download date of the last downloaded episode | ||
| 34 | + pub last_downloaded_episode_date: Option<DateTime<Utc>>, | ||
| 29 | } | 35 | } |
| 30 | 36 | ||
| 31 | impl Default for Feed { | 37 | impl Default for Feed { |
| @@ -39,6 +45,9 @@ impl Default for Feed { | |||
| 39 | last_episode_date: None, | 45 | last_episode_date: None, |
| 40 | enabled: true, | 46 | enabled: true, |
| 41 | tags: Vec::new(), | 47 | tags: Vec::new(), |
| 48 | + downloaded_episodes_count: 0, | ||
| 49 | + last_downloaded_episode_name: None, | ||
| 50 | + last_downloaded_episode_date: None, | ||
| 42 | } | 51 | } |
| 43 | } | 52 | } |
| 44 | } | 53 | } |
| @@ -55,6 +64,9 @@ impl Feed { | |||
| 55 | last_episode_date: None, | 64 | last_episode_date: None, |
| 56 | enabled: true, | 65 | enabled: true, |
| 57 | tags: Vec::new(), | 66 | tags: Vec::new(), |
| 67 | + downloaded_episodes_count: 0, | ||
| 68 | + last_downloaded_episode_name: None, | ||
| 69 | + last_downloaded_episode_date: None, | ||
| 58 | } | 70 | } |
| 59 | } | 71 | } |
| 60 | 72 | ||
modified
src/config/sync_task.rs +7 -1 | @@ -27,6 +27,12 @@ pub enum SyncMessage { | ||
| 27 | 27 | feed_name: String, |
| 28 | 28 | episodes_found: usize, |
| 29 | 29 | episodes_downloaded: usize, |
| 30 | + /// Updated download statistics for the feed | |
| 31 | + downloaded_episodes_count: usize, | |
| 32 | + last_downloaded_episode_name: Option<String>, | |
| 33 | + last_downloaded_episode_date: Option<chrono::DateTime<chrono::Utc>>, | |
| 34 | + /// Feed metadata updates | |
| 35 | + last_checked: Option<chrono::DateTime<chrono::Utc>>, | |
| 30 | 36 | }, |
| 31 | 37 | /// Sync failed with an error |
| 32 | 38 | SyncFailed { |
| @@ -251,7 +257,7 @@ impl SyncTaskManager { | ||
| 251 | 257 | } |
| 252 | 258 | ); |
| 253 | 259 | } |
| 254 | - SyncMessage::SyncCompleted { feed_id, feed_name: _, episodes_found, episodes_downloaded } => { | |
| 260 | + SyncMessage::SyncCompleted { feed_id, feed_name: _, episodes_found, episodes_downloaded, .. } => { | |
| 255 | 261 | self.status.update_feed_status( |
| 256 | 262 | feed_id.clone(), |
| 257 | 263 | FeedSyncStatus::Completed { |
| @@ -27,6 +27,12 @@ pub enum SyncMessage { | |||
| 27 | feed_name: String, | 27 | feed_name: String, |
| 28 | episodes_found: usize, | 28 | episodes_found: usize, |
| 29 | episodes_downloaded: usize, | 29 | episodes_downloaded: usize, |
| 30 | + /// Updated download statistics for the feed | ||
| 31 | + downloaded_episodes_count: usize, | ||
| 32 | + last_downloaded_episode_name: Option<String>, | ||
| 33 | + last_downloaded_episode_date: Option<chrono::DateTime<chrono::Utc>>, | ||
| 34 | + /// Feed metadata updates | ||
| 35 | + last_checked: Option<chrono::DateTime<chrono::Utc>>, | ||
| 30 | }, | 36 | }, |
| 31 | /// Sync failed with an error | 37 | /// Sync failed with an error |
| 32 | SyncFailed { | 38 | SyncFailed { |
| @@ -251,7 +257,7 @@ impl SyncTaskManager { | |||
| 251 | } | 257 | } |
| 252 | ); | 258 | ); |
| 253 | } | 259 | } |
| 254 | - SyncMessage::SyncCompleted { feed_id, feed_name: _, episodes_found, episodes_downloaded } => { | 260 | + SyncMessage::SyncCompleted { feed_id, feed_name: _, episodes_found, episodes_downloaded, .. } => { |
| 255 | self.status.update_feed_status( | 261 | self.status.update_feed_status( |
| 256 | feed_id.clone(), | 262 | feed_id.clone(), |
| 257 | FeedSyncStatus::Completed { | 263 | FeedSyncStatus::Completed { |
modified
src/rss/async_sync.rs +10 -0 | @@ -186,6 +186,12 @@ pub fn sync_feed_async(feed: Feed, sender: Sender<SyncMessage>) { | ||
| 186 | 186 | Ok(_) => { |
| 187 | 187 | info!("Successfully downloaded episode: {}", episode.title); |
| 188 | 188 | downloaded_count += 1; |
| 189 | + | |
| 190 | + // Update feed download statistics | |
| 191 | + feed_mut.downloaded_episodes_count += 1; | |
| 192 | + feed_mut.last_downloaded_episode_name = Some(episode.title.clone()); | |
| 193 | + feed_mut.last_downloaded_episode_date = Some(Utc::now()); | |
| 194 | + | |
| 189 | 195 | let _ = sender.send(SyncMessage::EpisodeDownloadCompleted { |
| 190 | 196 | feed_id: feed_id.clone(), |
| 191 | 197 | episode_title: episode.title.clone(), |
| @@ -217,6 +223,10 @@ pub fn sync_feed_async(feed: Feed, sender: Sender<SyncMessage>) { | ||
| 217 | 223 | feed_name: feed_name.clone(), |
| 218 | 224 | episodes_found: episodes.len(), |
| 219 | 225 | episodes_downloaded: downloaded_count, |
| 226 | + downloaded_episodes_count: feed_mut.downloaded_episodes_count, | |
| 227 | + last_downloaded_episode_name: feed_mut.last_downloaded_episode_name.clone(), | |
| 228 | + last_downloaded_episode_date: feed_mut.last_downloaded_episode_date, | |
| 229 | + last_checked: feed_mut.last_checked, | |
| 220 | 230 | }); |
| 221 | 231 | |
| 222 | 232 | info!("Async synchronization completed for feed: {} ({} episodes found, {} downloaded)", |
| @@ -186,6 +186,12 @@ pub fn sync_feed_async(feed: Feed, sender: Sender<SyncMessage>) { | |||
| 186 | Ok(_) => { | 186 | Ok(_) => { |
| 187 | info!("Successfully downloaded episode: {}", episode.title); | 187 | info!("Successfully downloaded episode: {}", episode.title); |
| 188 | downloaded_count += 1; | 188 | downloaded_count += 1; |
| 189 | + | ||
| 190 | + // Update feed download statistics | ||
| 191 | + feed_mut.downloaded_episodes_count += 1; | ||
| 192 | + feed_mut.last_downloaded_episode_name = Some(episode.title.clone()); | ||
| 193 | + feed_mut.last_downloaded_episode_date = Some(Utc::now()); | ||
| 194 | + | ||
| 189 | let _ = sender.send(SyncMessage::EpisodeDownloadCompleted { | 195 | let _ = sender.send(SyncMessage::EpisodeDownloadCompleted { |
| 190 | feed_id: feed_id.clone(), | 196 | feed_id: feed_id.clone(), |
| 191 | episode_title: episode.title.clone(), | 197 | episode_title: episode.title.clone(), |
| @@ -217,6 +223,10 @@ pub fn sync_feed_async(feed: Feed, sender: Sender<SyncMessage>) { | |||
| 217 | feed_name: feed_name.clone(), | 223 | feed_name: feed_name.clone(), |
| 218 | episodes_found: episodes.len(), | 224 | episodes_found: episodes.len(), |
| 219 | episodes_downloaded: downloaded_count, | 225 | episodes_downloaded: downloaded_count, |
| 226 | + downloaded_episodes_count: feed_mut.downloaded_episodes_count, | ||
| 227 | + last_downloaded_episode_name: feed_mut.last_downloaded_episode_name.clone(), | ||
| 228 | + last_downloaded_episode_date: feed_mut.last_downloaded_episode_date, | ||
| 229 | + last_checked: feed_mut.last_checked, | ||
| 220 | }); | 230 | }); |
| 221 | 231 | ||
| 222 | info!("Async synchronization completed for feed: {} ({} episodes found, {} downloaded)", | 232 | info!("Async synchronization completed for feed: {} ({} episodes found, {} downloaded)", |
modified
src/rss/sync.rs +6 -1 | @@ -113,7 +113,7 @@ pub fn sync_feed(feed: &mut Feed) -> Result<Vec<Episode>, SyncError> { | ||
| 113 | 113 | } |
| 114 | 114 | |
| 115 | 115 | /// Downloads new episodes that haven't been downloaded yet |
| 116 | -pub fn download_new_episodes(feed: &Feed, episodes: &[Episode]) -> Result<usize, SyncError> { | |
| 116 | +pub fn download_new_episodes(feed: &mut Feed, episodes: &[Episode]) -> Result<usize, SyncError> { | |
| 117 | 117 | let mut downloaded_count = 0; |
| 118 | 118 | |
| 119 | 119 | for episode in episodes { |
| @@ -128,6 +128,11 @@ pub fn download_new_episodes(feed: &Feed, episodes: &[Episode]) -> Result<usize, | ||
| 128 | 128 | Ok(_) => { |
| 129 | 129 | info!("Successfully downloaded episode: {}", episode.title); |
| 130 | 130 | downloaded_count += 1; |
| 131 | + | |
| 132 | + // Update feed download statistics | |
| 133 | + feed.downloaded_episodes_count += 1; | |
| 134 | + feed.last_downloaded_episode_name = Some(episode.title.clone()); | |
| 135 | + feed.last_downloaded_episode_date = Some(Utc::now()); | |
| 131 | 136 | } |
| 132 | 137 | Err(e) => { |
| 133 | 138 | error!("Failed to download episode {}: {}", episode.title, e); |
| @@ -113,7 +113,7 @@ pub fn sync_feed(feed: &mut Feed) -> Result<Vec<Episode>, SyncError> { | |||
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | /// Downloads new episodes that haven't been downloaded yet | 115 | /// Downloads new episodes that haven't been downloaded yet |
| 116 | -pub fn download_new_episodes(feed: &Feed, episodes: &[Episode]) -> Result<usize, SyncError> { | 116 | +pub fn download_new_episodes(feed: &mut Feed, episodes: &[Episode]) -> Result<usize, SyncError> { |
| 117 | let mut downloaded_count = 0; | 117 | let mut downloaded_count = 0; |
| 118 | 118 | ||
| 119 | for episode in episodes { | 119 | for episode in episodes { |
| @@ -128,6 +128,11 @@ pub fn download_new_episodes(feed: &Feed, episodes: &[Episode]) -> Result<usize, | |||
| 128 | Ok(_) => { | 128 | Ok(_) => { |
| 129 | info!("Successfully downloaded episode: {}", episode.title); | 129 | info!("Successfully downloaded episode: {}", episode.title); |
| 130 | downloaded_count += 1; | 130 | downloaded_count += 1; |
| 131 | + | ||
| 132 | + // Update feed download statistics | ||
| 133 | + feed.downloaded_episodes_count += 1; | ||
| 134 | + feed.last_downloaded_episode_name = Some(episode.title.clone()); | ||
| 135 | + feed.last_downloaded_episode_date = Some(Utc::now()); | ||
| 131 | } | 136 | } |
| 132 | Err(e) => { | 137 | Err(e) => { |
| 133 | error!("Failed to download episode {}: {}", episode.title, e); | 138 | error!("Failed to download episode {}: {}", episode.title, e); |
modified
src/ui/feed_list.rs +187 -96 | @@ -2,8 +2,8 @@ | ||
| 2 | 2 | //! |
| 3 | 3 | //! This module contains the feed list display and interaction logic. |
| 4 | 4 | |
| 5 | -use crate::config::AppState; | |
| 6 | -use egui::{Align, Layout, ScrollArea, Ui}; | |
| 5 | +use crate::config::{AppState, Theme}; | |
| 6 | +use egui::{Align, Color32, Layout, RichText, ScrollArea, Ui}; | |
| 7 | 7 | |
| 8 | 8 | /// Draws the feed list |
| 9 | 9 | pub fn draw_feed_list(state: &mut AppState, ui: &mut Ui) { |
| @@ -31,19 +31,20 @@ fn draw_feed_item(state: &mut AppState, ui: &mut Ui, index: usize) { | ||
| 31 | 31 | let feed_enabled = feed.enabled; |
| 32 | 32 | let feed_last_checked = feed.last_checked; |
| 33 | 33 | let feed_tags = feed.tags.clone(); |
| 34 | + let feed_downloaded_count = feed.downloaded_episodes_count; | |
| 35 | + let feed_last_downloaded_name = feed.last_downloaded_episode_name.clone(); | |
| 36 | + let feed_last_downloaded_date = feed.last_downloaded_episode_date; | |
| 34 | 37 | let is_selected = state.current_feed == Some(index); |
| 38 | + let theme = state.get_theme(); | |
| 39 | + | |
| 40 | + // Theme-aware colors | |
| 41 | + let (text_primary, text_secondary, text_dim, | |
| 42 | + success_color, warning_color, info_color, | |
| 43 | + bg_selected, bg_normal, stroke_selected, stroke_normal) = | |
| 44 | + get_theme_colors(&theme, is_selected); | |
| 35 | 45 | |
| 36 | - let bg_color = if is_selected { | |
| 37 | - egui::Color32::from_rgb(65, 72, 88) | |
| 38 | - } else { | |
| 39 | - egui::Color32::from_rgb(46, 52, 72) | |
| 40 | - }; | |
| 41 | - | |
| 42 | - let stroke_color = if is_selected { | |
| 43 | - egui::Color32::from_rgb(130, 170, 250) | |
| 44 | - } else { | |
| 45 | - egui::Color32::from_rgb(70, 78, 95) | |
| 46 | - }; | |
| 46 | + let bg_color = if is_selected { bg_selected } else { bg_normal }; | |
| 47 | + let stroke_color = if is_selected { stroke_selected } else { stroke_normal }; | |
| 47 | 48 | |
| 48 | 49 | // Frame avec style amélioré |
| 49 | 50 | egui::Frame { |
| @@ -54,104 +55,194 @@ fn draw_feed_item(state: &mut AppState, ui: &mut Ui, index: usize) { | ||
| 54 | 55 | ..Default::default() |
| 55 | 56 | } |
| 56 | 57 | .show(ui, |ui| { |
| 57 | - ui.spacing_mut().item_spacing = egui::Vec2::new(16.0, 8.0); | |
| 58 | + ui.spacing_mut().item_spacing = egui::Vec2::new(16.0, 8.0); | |
| 59 | + | |
| 60 | + ui.horizontal(|ui| { | |
| 61 | + // Section info principale | |
| 62 | + ui.vertical(|ui| { | |
| 63 | + // Nom du flux (plus gros et en gras) | |
| 64 | + ui.label(RichText::new(&feed_name) | |
| 65 | + .size(16.0) | |
| 66 | + .strong() | |
| 67 | + .color(text_primary)); | |
| 68 | + | |
| 69 | + // URL (plus petite) | |
| 70 | + ui.label(RichText::new(format!("🔗 {}", feed_url)) | |
| 71 | + .size(12.0) | |
| 72 | + .color(text_secondary)); | |
| 73 | + | |
| 74 | + // Dossier | |
| 75 | + ui.label(RichText::new(format!("📁 {}", feed_path)) | |
| 76 | + .size(12.0) | |
| 77 | + .color(text_secondary)); | |
| 78 | + | |
| 79 | + // Tags | |
| 80 | + if !feed_tags.is_empty() { | |
| 81 | + ui.horizontal_wrapped(|ui| { | |
| 82 | + ui.label(RichText::new("🏷️ ").size(12.0).color(text_dim)); | |
| 83 | + for tag in &feed_tags { | |
| 84 | + ui.label(RichText::new(format!("#{} ", tag)) | |
| 85 | + .size(11.0) | |
| 86 | + .color(info_color)); | |
| 87 | + } | |
| 88 | + }); | |
| 89 | + } | |
| 90 | + }); | |
| 58 | 91 | |
| 59 | - ui.horizontal(|ui| { | |
| 60 | - // Section info principale | |
| 61 | - ui.vertical(|ui| { | |
| 62 | - // Nom du flux (plus gros et en gras) | |
| 63 | - ui.label(egui::RichText::new(&feed_name) | |
| 64 | - .size(16.0) | |
| 65 | - .strong() | |
| 66 | - .color(egui::Color32::from_rgb(220, 230, 245))); | |
| 67 | - | |
| 68 | - // URL (plus petite) | |
| 69 | - ui.label(egui::RichText::new(format!("🔗 {}", feed_url)) | |
| 70 | - .size(12.0) | |
| 71 | - .color(egui::Color32::from_rgb(150, 170, 200))); | |
| 72 | - | |
| 73 | - // Dossier | |
| 74 | - ui.label(egui::RichText::new(format!("📁 {}", feed_path)) | |
| 75 | - .size(12.0) | |
| 76 | - .color(egui::Color32::from_rgb(150, 170, 200))); | |
| 77 | - | |
| 78 | - // Tags | |
| 79 | - if !feed_tags.is_empty() { | |
| 80 | - ui.horizontal_wrapped(|ui| { | |
| 81 | - ui.label(egui::RichText::new("🏷️ ").size(12.0)); | |
| 82 | - for tag in &feed_tags { | |
| 83 | - ui.label(egui::RichText::new(format!("#{} ", tag)) | |
| 84 | - .size(11.0) | |
| 85 | - .color(egui::Color32::from_rgb(180, 190, 220))); | |
| 86 | - } | |
| 87 | - }); | |
| 88 | - } | |
| 89 | - }); | |
| 92 | + // Section statut et statistiques | |
| 93 | + ui.vertical(|ui| { | |
| 94 | + ui.add_space(4.0); | |
| 90 | 95 | |
| 91 | - // Section statut | |
| 92 | - ui.vertical(|ui| { | |
| 93 | - ui.add_space(4.0); | |
| 94 | - | |
| 95 | - // Statut Activé/Désactivé | |
| 96 | - let status_text = if feed_enabled { | |
| 97 | - egui::RichText::new("✅ Activé").color(egui::Color32::from_rgb(80, 220, 100)) | |
| 98 | - } else { | |
| 99 | - egui::RichText::new("❌ Désactivé").color(egui::Color32::from_rgb(220, 80, 80)) | |
| 100 | - }; | |
| 101 | - ui.label(status_text.size(12.0)); | |
| 102 | - | |
| 103 | - // Dernière vérification | |
| 104 | - if let Some(date) = feed_last_checked { | |
| 105 | - ui.label(egui::RichText::new(format!("🔍 Vérifié: {}", date.format("%d/%m %H:%M"))) | |
| 96 | + // Statut Activé/Désactivé | |
| 97 | + let status_text = if feed_enabled { | |
| 98 | + RichText::new("✅ Activé").color(success_color) | |
| 99 | + } else { | |
| 100 | + RichText::new("❌ Désactivé").color(warning_color) | |
| 101 | + }; | |
| 102 | + ui.label(status_text.size(12.0)); | |
| 103 | + | |
| 104 | + // Dernière vérification | |
| 105 | + if let Some(date) = feed_last_checked { | |
| 106 | + ui.label(RichText::new(format!("🔍 Vérifié: {}", date.format("%d/%m %H:%M"))) | |
| 107 | + .size(11.0) | |
| 108 | + .color(text_secondary)); | |
| 109 | + } else { | |
| 110 | + ui.label(RichText::new("🔍 Jamais vérifié").size(11.0).color(text_dim)); | |
| 111 | + } | |
| 112 | + | |
| 113 | + // Séparateur visuel | |
| 114 | + ui.add_space(6.0); | |
| 115 | + ui.separator(); | |
| 116 | + ui.add_space(4.0); | |
| 117 | + | |
| 118 | + // Section Statistiques de téléchargement (mise en évidence) | |
| 119 | + ui.label(RichText::new("📊 Téléchargements").size(11.0).strong().color(info_color)) | |
| 120 | + .on_hover_text("Nombre et dernier épisode téléchargé"); | |
| 121 | + | |
| 122 | + // Nombre de téléchargements | |
| 123 | + if feed_downloaded_count > 0 { | |
| 124 | + ui.label(RichText::new(format!("📥 {} épisodes", feed_downloaded_count)) | |
| 125 | + .size(11.0) | |
| 126 | + .color(success_color)); | |
| 127 | + } else { | |
| 128 | + ui.label(RichText::new("📥 Aucun téléchargement").size(11.0).color(text_dim)); | |
| 129 | + } | |
| 130 | + | |
| 131 | + // Dernier épisode téléchargé | |
| 132 | + if let Some(last_name) = feed_last_downloaded_name { | |
| 133 | + if let Some(last_date) = feed_last_downloaded_date { | |
| 134 | + // Tronquer le nom si trop long | |
| 135 | + let display_name = if last_name.len() > 25 { | |
| 136 | + format!("{}...", &last_name[..22]) | |
| 137 | + } else { | |
| 138 | + last_name | |
| 139 | + }; | |
| 140 | + ui.label(RichText::new(format!("🎵 {}", display_name)) | |
| 106 | 141 | .size(11.0) |
| 107 | - .color(egui::Color32::from_rgb(160, 180, 200))); | |
| 142 | + .color(info_color)); | |
| 143 | + ui.label(RichText::new(format!(" le {}", last_date.format("%d/%m %H:%M"))) | |
| 144 | + .size(10.0) | |
| 145 | + .color(text_dim)); | |
| 108 | 146 | } else { |
| 109 | - ui.label(egui::RichText::new("🔍 Jamais vérifié").size(11.0).color(egui::Color32::from_rgb(160, 180, 200))); | |
| 147 | + let display_name = if last_name.len() > 25 { | |
| 148 | + format!("{}...", &last_name[..22]) | |
| 149 | + } else { | |
| 150 | + last_name | |
| 151 | + }; | |
| 152 | + ui.label(RichText::new(format!("🎵 {}", display_name)) | |
| 153 | + .size(11.0) | |
| 154 | + .color(info_color)); | |
| 110 | 155 | } |
| 111 | - }); | |
| 156 | + } | |
| 157 | + }); | |
| 158 | + | |
| 159 | + // Section actions (à droite) | |
| 160 | + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | |
| 161 | + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); | |
| 112 | 162 | |
| 113 | - // Section actions (à droite) | |
| 114 | - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | |
| 115 | - ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); | |
| 116 | - | |
| 117 | - // Bouton Synchroniser | |
| 118 | - if ui.button(egui::RichText::new("🔄").size(18.0)) | |
| 119 | - .on_hover_text("Synchroniser ce flux") | |
| 120 | - .clicked() { | |
| 121 | - sync_feed_async(state, index); | |
| 122 | - } | |
| 123 | - | |
| 124 | - // Bouton Éditer | |
| 125 | - if ui.button(egui::RichText::new("✏️").size(18.0)) | |
| 126 | - .on_hover_text("Éditer ce flux") | |
| 127 | - .clicked() { | |
| 128 | - state.editing_feed_index = Some(index); | |
| 129 | - state.load_edit_feed_form(index); | |
| 130 | - state.show_edit_feed_dialog = true; | |
| 131 | - } | |
| 132 | - | |
| 133 | - // Bouton Supprimer | |
| 134 | - if ui.button(egui::RichText::new("🗑️").size(18.0)) | |
| 135 | - .on_hover_text("Supprimer ce flux") | |
| 136 | - .clicked() { | |
| 137 | - log::info!("Deleting feed at index {}: {}", index, feed_name); | |
| 138 | - state.feeds.remove(index); | |
| 139 | - super::super::config::storage::save_feeds_default(&state.feeds).ok(); | |
| 140 | - log::info!("Feed deleted successfully"); | |
| 141 | - state.notification_manager.success("Flux supprimé"); | |
| 142 | - if state.current_feed == Some(index) { | |
| 143 | - state.select_feed(None); | |
| 144 | - } | |
| 163 | + // Bouton Synchroniser | |
| 164 | + if ui.button(RichText::new("🔄").size(18.0)) | |
| 165 | + .on_hover_text("Synchroniser ce flux") | |
| 166 | + .clicked() { | |
| 167 | + sync_feed_async(state, index); | |
| 168 | + } | |
| 169 | + | |
| 170 | + // Bouton Éditer | |
| 171 | + if ui.button(RichText::new("✏️").size(18.0)) | |
| 172 | + .on_hover_text("Éditer ce flux") | |
| 173 | + .clicked() { | |
| 174 | + state.editing_feed_index = Some(index); | |
| 175 | + state.load_edit_feed_form(index); | |
| 176 | + state.show_edit_feed_dialog = true; | |
| 177 | + } | |
| 178 | + | |
| 179 | + // Bouton Supprimer | |
| 180 | + if ui.button(RichText::new("🗑️").size(18.0)) | |
| 181 | + .on_hover_text("Supprimer ce flux") | |
| 182 | + .clicked() { | |
| 183 | + log::info!("Deleting feed at index {}: {}", index, feed_name); | |
| 184 | + state.feeds.remove(index); | |
| 185 | + super::super::config::storage::save_feeds_default(&state.feeds).ok(); | |
| 186 | + log::info!("Feed deleted successfully"); | |
| 187 | + state.notification_manager.success("Flux supprimé"); | |
| 188 | + if state.current_feed == Some(index) { | |
| 189 | + state.select_feed(None); | |
| 145 | 190 | } |
| 146 | - }); | |
| 191 | + } | |
| 147 | 192 | }); |
| 148 | 193 | }); |
| 194 | + }); | |
| 149 | 195 | |
| 150 | 196 | ui.add_space(6.0); |
| 151 | 197 | |
| 152 | 198 | ui.separator(); |
| 153 | 199 | } |
| 154 | 200 | |
| 201 | +/// Returns theme-appropriate colors for feed list items | |
| 202 | +/// Colors are aligned with the main theme colors from app.rs | |
| 203 | +fn get_theme_colors(theme: &Theme, _is_selected: bool) -> ( | |
| 204 | + Color32, Color32, Color32, // text_primary, text_secondary, text_dim | |
| 205 | + Color32, Color32, Color32, // success_color, warning_color, info_color | |
| 206 | + Color32, Color32, Color32, Color32 // bg_selected, bg_normal, stroke_selected, stroke_normal | |
| 207 | +) { | |
| 208 | + match theme { | |
| 209 | + Theme::Dark => { | |
| 210 | + let text_primary = Color32::from_rgb(220, 230, 245); | |
| 211 | + let text_secondary = Color32::from_rgb(180, 195, 210); | |
| 212 | + let text_dim = Color32::from_rgb(120, 140, 160); | |
| 213 | + let success_color = Color32::from_rgb(80, 220, 100); | |
| 214 | + let warning_color = Color32::from_rgb(245, 166, 35); | |
| 215 | + let info_color = Color32::from_rgb(130, 170, 250); | |
| 216 | + // Match theme colors from app.rs: bg_secondary = (46, 52, 72), bg_tertiary = (56, 62, 80) | |
| 217 | + let bg_normal = Color32::from_rgb(46, 52, 72); | |
| 218 | + let bg_selected = Color32::from_rgb(56, 62, 80); | |
| 219 | + let stroke_normal = Color32::from_rgb(80, 88, 106); | |
| 220 | + let stroke_selected = Color32::from_rgb(130, 170, 250); | |
| 221 | + | |
| 222 | + (text_primary, text_secondary, text_dim, | |
| 223 | + success_color, warning_color, info_color, | |
| 224 | + bg_selected, bg_normal, stroke_selected, stroke_normal) | |
| 225 | + } | |
| 226 | + Theme::Light => { | |
| 227 | + let text_primary = Color32::from_rgb(40, 42, 54); | |
| 228 | + let text_secondary = Color32::from_rgb(80, 90, 110); | |
| 229 | + let text_dim = Color32::from_rgb(120, 130, 150); | |
| 230 | + let success_color = Color32::from_rgb(20, 160, 80); | |
| 231 | + let warning_color = Color32::from_rgb(210, 120, 0); | |
| 232 | + let info_color = Color32::from_rgb(60, 100, 220); | |
| 233 | + // Match theme colors from app.rs: bg_secondary = (220, 230, 245), bg_tertiary = (200, 220, 240) | |
| 234 | + let bg_normal = Color32::from_rgb(220, 230, 245); | |
| 235 | + let bg_selected = Color32::from_rgb(200, 220, 240); | |
| 236 | + let stroke_normal = Color32::from_rgb(180, 195, 210); | |
| 237 | + let stroke_selected = Color32::from_rgb(100, 140, 220); | |
| 238 | + | |
| 239 | + (text_primary, text_secondary, text_dim, | |
| 240 | + success_color, warning_color, info_color, | |
| 241 | + bg_selected, bg_normal, stroke_selected, stroke_normal) | |
| 242 | + } | |
| 243 | + } | |
| 244 | +} | |
| 245 | + | |
| 155 | 246 | /// Synchronizes a single feed asynchronously |
| 156 | 247 | fn sync_feed_async(state: &mut AppState, index: usize) { |
| 157 | 248 | if index >= state.feeds.len() { |
| @@ -2,8 +2,8 @@ | |||
| 2 | //! | 2 | //! |
| 3 | //! This module contains the feed list display and interaction logic. | 3 | //! This module contains the feed list display and interaction logic. |
| 4 | 4 | ||
| 5 | -use crate::config::AppState; | 5 | +use crate::config::{AppState, Theme}; |
| 6 | -use egui::{Align, Layout, ScrollArea, Ui}; | 6 | +use egui::{Align, Color32, Layout, RichText, ScrollArea, Ui}; |
| 7 | 7 | ||
| 8 | /// Draws the feed list | 8 | /// Draws the feed list |
| 9 | pub fn draw_feed_list(state: &mut AppState, ui: &mut Ui) { | 9 | pub fn draw_feed_list(state: &mut AppState, ui: &mut Ui) { |
| @@ -31,19 +31,20 @@ fn draw_feed_item(state: &mut AppState, ui: &mut Ui, index: usize) { | |||
| 31 | let feed_enabled = feed.enabled; | 31 | let feed_enabled = feed.enabled; |
| 32 | let feed_last_checked = feed.last_checked; | 32 | let feed_last_checked = feed.last_checked; |
| 33 | let feed_tags = feed.tags.clone(); | 33 | let feed_tags = feed.tags.clone(); |
| 34 | + let feed_downloaded_count = feed.downloaded_episodes_count; | ||
| 35 | + let feed_last_downloaded_name = feed.last_downloaded_episode_name.clone(); | ||
| 36 | + let feed_last_downloaded_date = feed.last_downloaded_episode_date; | ||
| 34 | let is_selected = state.current_feed == Some(index); | 37 | let is_selected = state.current_feed == Some(index); |
| 38 | + let theme = state.get_theme(); | ||
| 39 | + | ||
| 40 | + // Theme-aware colors | ||
| 41 | + let (text_primary, text_secondary, text_dim, | ||
| 42 | + success_color, warning_color, info_color, | ||
| 43 | + bg_selected, bg_normal, stroke_selected, stroke_normal) = | ||
| 44 | + get_theme_colors(&theme, is_selected); | ||
| 35 | 45 | ||
| 36 | - let bg_color = if is_selected { | 46 | + let bg_color = if is_selected { bg_selected } else { bg_normal }; |
| 37 | - egui::Color32::from_rgb(65, 72, 88) | 47 | + let stroke_color = if is_selected { stroke_selected } else { stroke_normal }; |
| 38 | - } else { | ||
| 39 | - egui::Color32::from_rgb(46, 52, 72) | ||
| 40 | - }; | ||
| 41 | - | ||
| 42 | - let stroke_color = if is_selected { | ||
| 43 | - egui::Color32::from_rgb(130, 170, 250) | ||
| 44 | - } else { | ||
| 45 | - egui::Color32::from_rgb(70, 78, 95) | ||
| 46 | - }; | ||
| 47 | 48 | ||
| 48 | // Frame avec style amélioré | 49 | // Frame avec style amélioré |
| 49 | egui::Frame { | 50 | egui::Frame { |
| @@ -54,104 +55,194 @@ fn draw_feed_item(state: &mut AppState, ui: &mut Ui, index: usize) { | |||
| 54 | ..Default::default() | 55 | ..Default::default() |
| 55 | } | 56 | } |
| 56 | .show(ui, |ui| { | 57 | .show(ui, |ui| { |
| 57 | - ui.spacing_mut().item_spacing = egui::Vec2::new(16.0, 8.0); | 58 | + ui.spacing_mut().item_spacing = egui::Vec2::new(16.0, 8.0); |
| 59 | + | ||
| 60 | + ui.horizontal(|ui| { | ||
| 61 | + // Section info principale | ||
| 62 | + ui.vertical(|ui| { | ||
| 63 | + // Nom du flux (plus gros et en gras) | ||
| 64 | + ui.label(RichText::new(&feed_name) | ||
| 65 | + .size(16.0) | ||
| 66 | + .strong() | ||
| 67 | + .color(text_primary)); | ||
| 68 | + | ||
| 69 | + // URL (plus petite) | ||
| 70 | + ui.label(RichText::new(format!("🔗 {}", feed_url)) | ||
| 71 | + .size(12.0) | ||
| 72 | + .color(text_secondary)); | ||
| 73 | + | ||
| 74 | + // Dossier | ||
| 75 | + ui.label(RichText::new(format!("📁 {}", feed_path)) | ||
| 76 | + .size(12.0) | ||
| 77 | + .color(text_secondary)); | ||
| 78 | + | ||
| 79 | + // Tags | ||
| 80 | + if !feed_tags.is_empty() { | ||
| 81 | + ui.horizontal_wrapped(|ui| { | ||
| 82 | + ui.label(RichText::new("🏷️ ").size(12.0).color(text_dim)); | ||
| 83 | + for tag in &feed_tags { | ||
| 84 | + ui.label(RichText::new(format!("#{} ", tag)) | ||
| 85 | + .size(11.0) | ||
| 86 | + .color(info_color)); | ||
| 87 | + } | ||
| 88 | + }); | ||
| 89 | + } | ||
| 90 | + }); | ||
| 58 | 91 | ||
| 59 | - ui.horizontal(|ui| { | 92 | + // Section statut et statistiques |
| 60 | - // Section info principale | 93 | + ui.vertical(|ui| { |
| 61 | - ui.vertical(|ui| { | 94 | + ui.add_space(4.0); |
| 62 | - // Nom du flux (plus gros et en gras) | ||
| 63 | - ui.label(egui::RichText::new(&feed_name) | ||
| 64 | - .size(16.0) | ||
| 65 | - .strong() | ||
| 66 | - .color(egui::Color32::from_rgb(220, 230, 245))); | ||
| 67 | - | ||
| 68 | - // URL (plus petite) | ||
| 69 | - ui.label(egui::RichText::new(format!("🔗 {}", feed_url)) | ||
| 70 | - .size(12.0) | ||
| 71 | - .color(egui::Color32::from_rgb(150, 170, 200))); | ||
| 72 | - | ||
| 73 | - // Dossier | ||
| 74 | - ui.label(egui::RichText::new(format!("📁 {}", feed_path)) | ||
| 75 | - .size(12.0) | ||
| 76 | - .color(egui::Color32::from_rgb(150, 170, 200))); | ||
| 77 | - | ||
| 78 | - // Tags | ||
| 79 | - if !feed_tags.is_empty() { | ||
| 80 | - ui.horizontal_wrapped(|ui| { | ||
| 81 | - ui.label(egui::RichText::new("🏷️ ").size(12.0)); | ||
| 82 | - for tag in &feed_tags { | ||
| 83 | - ui.label(egui::RichText::new(format!("#{} ", tag)) | ||
| 84 | - .size(11.0) | ||
| 85 | - .color(egui::Color32::from_rgb(180, 190, 220))); | ||
| 86 | - } | ||
| 87 | - }); | ||
| 88 | - } | ||
| 89 | - }); | ||
| 90 | 95 | ||
| 91 | - // Section statut | 96 | + // Statut Activé/Désactivé |
| 92 | - ui.vertical(|ui| { | 97 | + let status_text = if feed_enabled { |
| 93 | - ui.add_space(4.0); | 98 | + RichText::new("✅ Activé").color(success_color) |
| 94 | - | 99 | + } else { |
| 95 | - // Statut Activé/Désactivé | 100 | + RichText::new("❌ Désactivé").color(warning_color) |
| 96 | - let status_text = if feed_enabled { | 101 | + }; |
| 97 | - egui::RichText::new("✅ Activé").color(egui::Color32::from_rgb(80, 220, 100)) | 102 | + ui.label(status_text.size(12.0)); |
| 98 | - } else { | 103 | + |
| 99 | - egui::RichText::new("❌ Désactivé").color(egui::Color32::from_rgb(220, 80, 80)) | 104 | + // Dernière vérification |
| 100 | - }; | 105 | + if let Some(date) = feed_last_checked { |
| 101 | - ui.label(status_text.size(12.0)); | 106 | + ui.label(RichText::new(format!("🔍 Vérifié: {}", date.format("%d/%m %H:%M"))) |
| 102 | - | 107 | + .size(11.0) |
| 103 | - // Dernière vérification | 108 | + .color(text_secondary)); |
| 104 | - if let Some(date) = feed_last_checked { | 109 | + } else { |
| 105 | - ui.label(egui::RichText::new(format!("🔍 Vérifié: {}", date.format("%d/%m %H:%M"))) | 110 | + ui.label(RichText::new("🔍 Jamais vérifié").size(11.0).color(text_dim)); |
| 111 | + } | ||
| 112 | + | ||
| 113 | + // Séparateur visuel | ||
| 114 | + ui.add_space(6.0); | ||
| 115 | + ui.separator(); | ||
| 116 | + ui.add_space(4.0); | ||
| 117 | + | ||
| 118 | + // Section Statistiques de téléchargement (mise en évidence) | ||
| 119 | + ui.label(RichText::new("📊 Téléchargements").size(11.0).strong().color(info_color)) | ||
| 120 | + .on_hover_text("Nombre et dernier épisode téléchargé"); | ||
| 121 | + | ||
| 122 | + // Nombre de téléchargements | ||
| 123 | + if feed_downloaded_count > 0 { | ||
| 124 | + ui.label(RichText::new(format!("📥 {} épisodes", feed_downloaded_count)) | ||
| 125 | + .size(11.0) | ||
| 126 | + .color(success_color)); | ||
| 127 | + } else { | ||
| 128 | + ui.label(RichText::new("📥 Aucun téléchargement").size(11.0).color(text_dim)); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + // Dernier épisode téléchargé | ||
| 132 | + if let Some(last_name) = feed_last_downloaded_name { | ||
| 133 | + if let Some(last_date) = feed_last_downloaded_date { | ||
| 134 | + // Tronquer le nom si trop long | ||
| 135 | + let display_name = if last_name.len() > 25 { | ||
| 136 | + format!("{}...", &last_name[..22]) | ||
| 137 | + } else { | ||
| 138 | + last_name | ||
| 139 | + }; | ||
| 140 | + ui.label(RichText::new(format!("🎵 {}", display_name)) | ||
| 106 | .size(11.0) | 141 | .size(11.0) |
| 107 | - .color(egui::Color32::from_rgb(160, 180, 200))); | 142 | + .color(info_color)); |
| 143 | + ui.label(RichText::new(format!(" le {}", last_date.format("%d/%m %H:%M"))) | ||
| 144 | + .size(10.0) | ||
| 145 | + .color(text_dim)); | ||
| 108 | } else { | 146 | } else { |
| 109 | - ui.label(egui::RichText::new("🔍 Jamais vérifié").size(11.0).color(egui::Color32::from_rgb(160, 180, 200))); | 147 | + let display_name = if last_name.len() > 25 { |
| 148 | + format!("{}...", &last_name[..22]) | ||
| 149 | + } else { | ||
| 150 | + last_name | ||
| 151 | + }; | ||
| 152 | + ui.label(RichText::new(format!("🎵 {}", display_name)) | ||
| 153 | + .size(11.0) | ||
| 154 | + .color(info_color)); | ||
| 110 | } | 155 | } |
| 111 | - }); | 156 | + } |
| 157 | + }); | ||
| 158 | + | ||
| 159 | + // Section actions (à droite) | ||
| 160 | + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | ||
| 161 | + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); | ||
| 112 | 162 | ||
| 113 | - // Section actions (à droite) | 163 | + // Bouton Synchroniser |
| 114 | - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | 164 | + if ui.button(RichText::new("🔄").size(18.0)) |
| 115 | - ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); | 165 | + .on_hover_text("Synchroniser ce flux") |
| 116 | - | 166 | + .clicked() { |
| 117 | - // Bouton Synchroniser | 167 | + sync_feed_async(state, index); |
| 118 | - if ui.button(egui::RichText::new("🔄").size(18.0)) | 168 | + } |
| 119 | - .on_hover_text("Synchroniser ce flux") | 169 | + |
| 120 | - .clicked() { | 170 | + // Bouton Éditer |
| 121 | - sync_feed_async(state, index); | 171 | + if ui.button(RichText::new("✏️").size(18.0)) |
| 122 | - } | 172 | + .on_hover_text("Éditer ce flux") |
| 123 | - | 173 | + .clicked() { |
| 124 | - // Bouton Éditer | 174 | + state.editing_feed_index = Some(index); |
| 125 | - if ui.button(egui::RichText::new("✏️").size(18.0)) | 175 | + state.load_edit_feed_form(index); |
| 126 | - .on_hover_text("Éditer ce flux") | 176 | + state.show_edit_feed_dialog = true; |
| 127 | - .clicked() { | 177 | + } |
| 128 | - state.editing_feed_index = Some(index); | 178 | + |
| 129 | - state.load_edit_feed_form(index); | 179 | + // Bouton Supprimer |
| 130 | - state.show_edit_feed_dialog = true; | 180 | + if ui.button(RichText::new("🗑️").size(18.0)) |
| 131 | - } | 181 | + .on_hover_text("Supprimer ce flux") |
| 132 | - | 182 | + .clicked() { |
| 133 | - // Bouton Supprimer | 183 | + log::info!("Deleting feed at index {}: {}", index, feed_name); |
| 134 | - if ui.button(egui::RichText::new("🗑️").size(18.0)) | 184 | + state.feeds.remove(index); |
| 135 | - .on_hover_text("Supprimer ce flux") | 185 | + super::super::config::storage::save_feeds_default(&state.feeds).ok(); |
| 136 | - .clicked() { | 186 | + log::info!("Feed deleted successfully"); |
| 137 | - log::info!("Deleting feed at index {}: {}", index, feed_name); | 187 | + state.notification_manager.success("Flux supprimé"); |
| 138 | - state.feeds.remove(index); | 188 | + if state.current_feed == Some(index) { |
| 139 | - super::super::config::storage::save_feeds_default(&state.feeds).ok(); | 189 | + state.select_feed(None); |
| 140 | - log::info!("Feed deleted successfully"); | ||
| 141 | - state.notification_manager.success("Flux supprimé"); | ||
| 142 | - if state.current_feed == Some(index) { | ||
| 143 | - state.select_feed(None); | ||
| 144 | - } | ||
| 145 | } | 190 | } |
| 146 | - }); | 191 | + } |
| 147 | }); | 192 | }); |
| 148 | }); | 193 | }); |
| 194 | + }); | ||
| 149 | 195 | ||
| 150 | ui.add_space(6.0); | 196 | ui.add_space(6.0); |
| 151 | 197 | ||
| 152 | ui.separator(); | 198 | ui.separator(); |
| 153 | } | 199 | } |
| 154 | 200 | ||
| 201 | +/// Returns theme-appropriate colors for feed list items | ||
| 202 | +/// Colors are aligned with the main theme colors from app.rs | ||
| 203 | +fn get_theme_colors(theme: &Theme, _is_selected: bool) -> ( | ||
| 204 | + Color32, Color32, Color32, // text_primary, text_secondary, text_dim | ||
| 205 | + Color32, Color32, Color32, // success_color, warning_color, info_color | ||
| 206 | + Color32, Color32, Color32, Color32 // bg_selected, bg_normal, stroke_selected, stroke_normal | ||
| 207 | +) { | ||
| 208 | + match theme { | ||
| 209 | + Theme::Dark => { | ||
| 210 | + let text_primary = Color32::from_rgb(220, 230, 245); | ||
| 211 | + let text_secondary = Color32::from_rgb(180, 195, 210); | ||
| 212 | + let text_dim = Color32::from_rgb(120, 140, 160); | ||
| 213 | + let success_color = Color32::from_rgb(80, 220, 100); | ||
| 214 | + let warning_color = Color32::from_rgb(245, 166, 35); | ||
| 215 | + let info_color = Color32::from_rgb(130, 170, 250); | ||
| 216 | + // Match theme colors from app.rs: bg_secondary = (46, 52, 72), bg_tertiary = (56, 62, 80) | ||
| 217 | + let bg_normal = Color32::from_rgb(46, 52, 72); | ||
| 218 | + let bg_selected = Color32::from_rgb(56, 62, 80); | ||
| 219 | + let stroke_normal = Color32::from_rgb(80, 88, 106); | ||
| 220 | + let stroke_selected = Color32::from_rgb(130, 170, 250); | ||
| 221 | + | ||
| 222 | + (text_primary, text_secondary, text_dim, | ||
| 223 | + success_color, warning_color, info_color, | ||
| 224 | + bg_selected, bg_normal, stroke_selected, stroke_normal) | ||
| 225 | + } | ||
| 226 | + Theme::Light => { | ||
| 227 | + let text_primary = Color32::from_rgb(40, 42, 54); | ||
| 228 | + let text_secondary = Color32::from_rgb(80, 90, 110); | ||
| 229 | + let text_dim = Color32::from_rgb(120, 130, 150); | ||
| 230 | + let success_color = Color32::from_rgb(20, 160, 80); | ||
| 231 | + let warning_color = Color32::from_rgb(210, 120, 0); | ||
| 232 | + let info_color = Color32::from_rgb(60, 100, 220); | ||
| 233 | + // Match theme colors from app.rs: bg_secondary = (220, 230, 245), bg_tertiary = (200, 220, 240) | ||
| 234 | + let bg_normal = Color32::from_rgb(220, 230, 245); | ||
| 235 | + let bg_selected = Color32::from_rgb(200, 220, 240); | ||
| 236 | + let stroke_normal = Color32::from_rgb(180, 195, 210); | ||
| 237 | + let stroke_selected = Color32::from_rgb(100, 140, 220); | ||
| 238 | + | ||
| 239 | + (text_primary, text_secondary, text_dim, | ||
| 240 | + success_color, warning_color, info_color, | ||
| 241 | + bg_selected, bg_normal, stroke_selected, stroke_normal) | ||
| 242 | + } | ||
| 243 | + } | ||
| 244 | +} | ||
| 245 | + | ||
| 155 | /// Synchronizes a single feed asynchronously | 246 | /// Synchronizes a single feed asynchronously |
| 156 | fn sync_feed_async(state: &mut AppState, index: usize) { | 247 | fn sync_feed_async(state: &mut AppState, index: usize) { |
| 157 | if index >= state.feeds.len() { | 248 | if index >= state.feeds.len() { |
modified
src/ui/home.rs +31 -0 | @@ -7,6 +7,37 @@ use egui::Ui; | ||
| 7 | 7 | |
| 8 | 8 | /// Draws the home screen |
| 9 | 9 | pub fn draw_home(state: &mut AppState, ui: &mut Ui) { |
| 10 | + // Process sync messages first to update feeds with latest download statistics | |
| 11 | + // This ensures the feed list displays up-to-date information | |
| 12 | + let messages = state.sync_task_manager.process_messages(); | |
| 13 | + let mut needs_save = false; | |
| 14 | + for message in messages { | |
| 15 | + if let crate::config::sync_task::SyncMessage::SyncCompleted { | |
| 16 | + feed_id, | |
| 17 | + downloaded_episodes_count, | |
| 18 | + last_downloaded_episode_name, | |
| 19 | + last_downloaded_episode_date, | |
| 20 | + last_checked, | |
| 21 | + .. | |
| 22 | + } = message { | |
| 23 | + // Find the feed and update its statistics | |
| 24 | + if let Some(feed) = state.feeds.iter_mut().find(|f| f.id == feed_id) { | |
| 25 | + feed.downloaded_episodes_count = downloaded_episodes_count; | |
| 26 | + feed.last_downloaded_episode_name = last_downloaded_episode_name; | |
| 27 | + feed.last_downloaded_episode_date = last_downloaded_episode_date; | |
| 28 | + feed.last_checked = last_checked; | |
| 29 | + needs_save = true; | |
| 30 | + } | |
| 31 | + } | |
| 32 | + } | |
| 33 | + | |
| 34 | + // Save feeds if any download statistics were updated | |
| 35 | + if needs_save { | |
| 36 | + if let Err(e) = crate::config::storage::save_feeds_default(&state.feeds) { | |
| 37 | + log::error!("Failed to save feeds after download: {}", e); | |
| 38 | + } | |
| 39 | + } | |
| 40 | + | |
| 10 | 41 | // Header avec titre et icône |
| 11 | 42 | ui.horizontal(|ui| { |
| 12 | 43 | ui.heading(egui::RichText::new("🎙️ RSS Podcast Downloader").color(egui::Color32::from_rgb(130, 170, 250))); |
| @@ -7,6 +7,37 @@ use egui::Ui; | |||
| 7 | 7 | ||
| 8 | /// Draws the home screen | 8 | /// Draws the home screen |
| 9 | pub fn draw_home(state: &mut AppState, ui: &mut Ui) { | 9 | pub fn draw_home(state: &mut AppState, ui: &mut Ui) { |
| 10 | + // Process sync messages first to update feeds with latest download statistics | ||
| 11 | + // This ensures the feed list displays up-to-date information | ||
| 12 | + let messages = state.sync_task_manager.process_messages(); | ||
| 13 | + let mut needs_save = false; | ||
| 14 | + for message in messages { | ||
| 15 | + if let crate::config::sync_task::SyncMessage::SyncCompleted { | ||
| 16 | + feed_id, | ||
| 17 | + downloaded_episodes_count, | ||
| 18 | + last_downloaded_episode_name, | ||
| 19 | + last_downloaded_episode_date, | ||
| 20 | + last_checked, | ||
| 21 | + .. | ||
| 22 | + } = message { | ||
| 23 | + // Find the feed and update its statistics | ||
| 24 | + if let Some(feed) = state.feeds.iter_mut().find(|f| f.id == feed_id) { | ||
| 25 | + feed.downloaded_episodes_count = downloaded_episodes_count; | ||
| 26 | + feed.last_downloaded_episode_name = last_downloaded_episode_name; | ||
| 27 | + feed.last_downloaded_episode_date = last_downloaded_episode_date; | ||
| 28 | + feed.last_checked = last_checked; | ||
| 29 | + needs_save = true; | ||
| 30 | + } | ||
| 31 | + } | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + // Save feeds if any download statistics were updated | ||
| 35 | + if needs_save { | ||
| 36 | + if let Err(e) = crate::config::storage::save_feeds_default(&state.feeds) { | ||
| 37 | + log::error!("Failed to save feeds after download: {}", e); | ||
| 38 | + } | ||
| 39 | + } | ||
| 40 | + | ||
| 10 | // Header avec titre et icône | 41 | // Header avec titre et icône |
| 11 | ui.horizontal(|ui| { | 42 | ui.horizontal(|ui| { |
| 12 | ui.heading(egui::RichText::new("🎙️ RSS Podcast Downloader").color(egui::Color32::from_rgb(130, 170, 250))); | 43 | ui.heading(egui::RichText::new("🎙️ RSS Podcast Downloader").color(egui::Color32::from_rgb(130, 170, 250))); |
modified
src/ui/status_bar.rs +2 -1 | @@ -58,10 +58,11 @@ pub fn draw_status_bar(state: &mut AppState, ui: &mut Ui) { | ||
| 58 | 58 | } |
| 59 | 59 | |
| 60 | 60 | // Sync status avec icône |
| 61 | + // Note: Sync messages are processed in home.rs before drawing | |
| 61 | 62 | let feed_count = state.feed_count(); |
| 62 | 63 | let enabled_count = state.enabled_feed_count(); |
| 63 | 64 | |
| 64 | - // Process sync messages | |
| 65 | + // Process remaining messages (for status display only) | |
| 65 | 66 | state.sync_task_manager.process_messages(); |
| 66 | 67 | |
| 67 | 68 | ui.horizontal(|ui| { |
| @@ -58,10 +58,11 @@ pub fn draw_status_bar(state: &mut AppState, ui: &mut Ui) { | |||
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | // Sync status avec icône | 60 | // Sync status avec icône |
| 61 | + // Note: Sync messages are processed in home.rs before drawing | ||
| 61 | let feed_count = state.feed_count(); | 62 | let feed_count = state.feed_count(); |
| 62 | let enabled_count = state.enabled_feed_count(); | 63 | let enabled_count = state.enabled_feed_count(); |
| 63 | 64 | ||
| 64 | - // Process sync messages | 65 | + // Process remaining messages (for status display only) |
| 65 | state.sync_task_manager.process_messages(); | 66 | state.sync_task_manager.process_messages(); |
| 66 | 67 | ||
| 67 | ui.horizontal(|ui| { | 68 | ui.horizontal(|ui| { |
modified
src/ui/sync_status.rs +1 -3 | @@ -48,9 +48,7 @@ fn draw_progress_bar(ui: &mut Ui, progress: f32, width: f32) { | ||
| 48 | 48 | |
| 49 | 49 | /// Draws a compact sync indicator for the status bar |
| 50 | 50 | pub fn draw_sync_indicator(state: &mut AppState, ui: &mut Ui) { |
| 51 | - // Process messages first | |
| 52 | - state.sync_task_manager.process_messages(); | |
| 53 | - | |
| 51 | + // Messages are processed in status_bar.rs, so we just display the status here | |
| 54 | 52 | let status = &state.sync_task_manager.status; |
| 55 | 53 | |
| 56 | 54 | // Always show sync info if there's any activity |
| @@ -48,9 +48,7 @@ fn draw_progress_bar(ui: &mut Ui, progress: f32, width: f32) { | |||
| 48 | 48 | ||
| 49 | /// Draws a compact sync indicator for the status bar | 49 | /// Draws a compact sync indicator for the status bar |
| 50 | pub fn draw_sync_indicator(state: &mut AppState, ui: &mut Ui) { | 50 | pub fn draw_sync_indicator(state: &mut AppState, ui: &mut Ui) { |
| 51 | - // Process messages first | 51 | + // Messages are processed in status_bar.rs, so we just display the status here |
| 52 | - state.sync_task_manager.process_messages(); | ||
| 53 | - | ||
| 54 | let status = &state.sync_task_manager.status; | 52 | let status = &state.sync_task_manager.status; |
| 55 | 53 | ||
| 56 | // Always show sync info if there's any activity | 54 | // Always show sync info if there's any activity |