msansm1/rss-podcast-dlpublic Fork
⑂ 1c88366
Commits
⬇ Clone ▾
git clone https://git.rickub.com/msansm1/rss-podcast-dl.git
git clone ssh://git@rickub.com/msansm1/rss-podcast-dl.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

corrections sur les indicateurs

msansm1 committed 2026-07-19T20:49:35+02:00 Browse files
1c88366 parent: b199364
modified src/config/feed.rs +12 -0
@@ -26,6 +26,12 @@ pub struct Feed {
2626 pub enabled: bool,
2727 /// Tags for categorizing the feed (e.g., "Tech", "News", "French")
2828 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>>,
2935 }
3036
3137 impl Default for Feed {
@@ -39,6 +45,9 @@ impl Default for Feed {
3945 last_episode_date: None,
4046 enabled: true,
4147 tags: Vec::new(),
48+ downloaded_episodes_count: 0,
49+ last_downloaded_episode_name: None,
50+ last_downloaded_episode_date: None,
4251 }
4352 }
4453 }
@@ -55,6 +64,9 @@ impl Feed {
5564 last_episode_date: None,
5665 enabled: true,
5766 tags: Vec::new(),
67+ downloaded_episodes_count: 0,
68+ last_downloaded_episode_name: None,
69+ last_downloaded_episode_date: None,
5870 }
5971 }
6072
@@ -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 {
2727 feed_name: String,
2828 episodes_found: usize,
2929 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>>,
3036 },
3137 /// Sync failed with an error
3238 SyncFailed {
@@ -251,7 +257,7 @@ impl SyncTaskManager {
251257 }
252258 );
253259 }
254- SyncMessage::SyncCompleted { feed_id, feed_name: _, episodes_found, episodes_downloaded } => {
260+ SyncMessage::SyncCompleted { feed_id, feed_name: _, episodes_found, episodes_downloaded, .. } => {
255261 self.status.update_feed_status(
256262 feed_id.clone(),
257263 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 error37 /// 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>) {
186186 Ok(_) => {
187187 info!("Successfully downloaded episode: {}", episode.title);
188188 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+
189195 let _ = sender.send(SyncMessage::EpisodeDownloadCompleted {
190196 feed_id: feed_id.clone(),
191197 episode_title: episode.title.clone(),
@@ -217,6 +223,10 @@ pub fn sync_feed_async(feed: Feed, sender: Sender<SyncMessage>) {
217223 feed_name: feed_name.clone(),
218224 episodes_found: episodes.len(),
219225 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,
220230 });
221231
222232 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> {
113113 }
114114
115115 /// 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> {
117117 let mut downloaded_count = 0;
118118
119119 for episode in episodes {
@@ -128,6 +128,11 @@ pub fn download_new_episodes(feed: &Feed, episodes: &[Episode]) -> Result<usize,
128128 Ok(_) => {
129129 info!("Successfully downloaded episode: {}", episode.title);
130130 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());
131136 }
132137 Err(e) => {
133138 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 yet115 /// 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 @@
22 //!
33 //! This module contains the feed list display and interaction logic.
44
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};
77
88 /// Draws the feed list
99 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) {
3131 let feed_enabled = feed.enabled;
3232 let feed_last_checked = feed.last_checked;
3333 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;
3437 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);
3545
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 };
4748
4849 // Frame avec style amélioré
4950 egui::Frame {
@@ -54,104 +55,194 @@ fn draw_feed_item(state: &mut AppState, ui: &mut Ui, index: usize) {
5455 ..Default::default()
5556 }
5657 .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+ });
5891
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);
9095
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))
106141 .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));
108146 } 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));
110155 }
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);
112162
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);
145190 }
146- });
191+ }
147192 });
148193 });
194+ });
149195
150196 ui.add_space(6.0);
151197
152198 ui.separator();
153199 }
154200
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+
155246 /// Synchronizes a single feed asynchronously
156247 fn sync_feed_async(state: &mut AppState, index: usize) {
157248 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 list8 /// 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 principale93+ 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 statut96+ // 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érification108+ .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 Synchroniser167+ 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 Éditer174+ 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 Supprimer183+ 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 asynchronously246 /// 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;
77
88 /// Draws the home screen
99 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+
1041 // Header avec titre et icône
1142 ui.horizontal(|ui| {
1243 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 screen8 /// 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ône41 // 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) {
5858 }
5959
6060 // Sync status avec icône
61+ // Note: Sync messages are processed in home.rs before drawing
6162 let feed_count = state.feed_count();
6263 let enabled_count = state.enabled_feed_count();
6364
64- // Process sync messages
65+ // Process remaining messages (for status display only)
6566 state.sync_task_manager.process_messages();
6667
6768 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ône60 // 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 messages65+ // 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) {
4848
4949 /// Draws a compact sync indicator for the status bar
5050 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
5452 let status = &state.sync_task_manager.status;
5553
5654 // 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 bar49 /// 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 first51+ // 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 activity54 // Always show sync info if there's any activity