msansm1/rss-podcast-dlpublic Fork
⑂ b199364
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

Fix sync status display issues

msansm1 committed 2026-07-10T22:10:42+02:00 Browse files
b199364 parent: e1b5362
modified src/config/sync_task.rs +38 -5
@@ -172,6 +172,13 @@ impl SyncStatus {
172172 self.global_sync_in_progress ||
173173 self.feed_sync_status.values().any(|s| matches!(s, FeedSyncStatus::InProgress { .. }))
174174 }
175+
176+ /// Check if there are any sync activities (in progress or recently completed)
177+ /// Used to determine if we should show sync-related UI elements
178+ pub fn has_sync_activity(&self) -> bool {
179+ self.global_sync_in_progress ||
180+ !self.feed_sync_status.is_empty()
181+ }
175182
176183 /// Get syncing feed count
177184 pub fn syncing_feed_count(&self) -> usize {
@@ -236,11 +243,6 @@ impl SyncTaskManager {
236243 );
237244 }
238245 SyncMessage::SyncProgress { feed_id, progress, message } => {
239- if let Some(status) = self.status.feed_sync_status.get_mut(feed_id) {
240- if let FeedSyncStatus::InProgress { progress: _, message: _ } = status {
241- // Update existing in-progress status
242- }
243- }
244246 self.status.update_feed_status(
245247 feed_id.clone(),
246248 FeedSyncStatus::InProgress {
@@ -257,6 +259,16 @@ impl SyncTaskManager {
257259 episodes_downloaded: *episodes_downloaded,
258260 }
259261 );
262+
263+ // Check if all feeds are completed
264+ if self.status.global_sync_in_progress {
265+ let all_completed = self.status.feed_sync_status.values().all(|s| {
266+ matches!(s, FeedSyncStatus::Completed { .. } | FeedSyncStatus::Failed { .. })
267+ });
268+ if all_completed {
269+ self.status.complete_global_sync();
270+ }
271+ }
260272 }
261273 SyncMessage::SyncFailed { feed_id, feed_name: _, error } => {
262274 self.status.update_feed_status(
@@ -265,6 +277,20 @@ impl SyncTaskManager {
265277 error: error.clone(),
266278 }
267279 );
280+
281+ // Check if all feeds are completed (including failed ones)
282+ if self.status.global_sync_in_progress {
283+ let all_completed = self.status.feed_sync_status.values().all(|s| {
284+ matches!(s, FeedSyncStatus::Completed { .. } | FeedSyncStatus::Failed { .. })
285+ });
286+ if all_completed {
287+ self.status.complete_global_sync();
288+ }
289+ }
290+
291+ // For single feed sync, we should also clean up after a failure
292+ // Remove the feed from tracking after a reasonable delay
293+ // This is handled by the is_syncing() check which looks at InProgress status
268294 }
269295 SyncMessage::EpisodeDownloadStarted { feed_id: _, episode_title: _ } => {
270296 // Episode download started - could track this if needed
@@ -310,6 +336,13 @@ impl SyncTaskManager {
310336 crate::rss::async_sync::sync_feed_async(feed, sender);
311337 });
312338 }
339+
340+ /// Clears completed and failed sync statuses
341+ pub fn clear_completed_sync_status(&mut self) {
342+ self.status.feed_sync_status.retain(|_, status| {
343+ matches!(status, FeedSyncStatus::InProgress { .. })
344+ });
345+ }
313346 }
314347
315348 impl Default for SyncTaskManager {
@@ -172,6 +172,13 @@ impl SyncStatus {
172 self.global_sync_in_progress || 172 self.global_sync_in_progress ||
173 self.feed_sync_status.values().any(|s| matches!(s, FeedSyncStatus::InProgress { .. }))173 self.feed_sync_status.values().any(|s| matches!(s, FeedSyncStatus::InProgress { .. }))
174 }174 }
175+
176+ /// Check if there are any sync activities (in progress or recently completed)
177+ /// Used to determine if we should show sync-related UI elements
178+ pub fn has_sync_activity(&self) -> bool {
179+ self.global_sync_in_progress ||
180+ !self.feed_sync_status.is_empty()
181+ }
175 182
176 /// Get syncing feed count183 /// Get syncing feed count
177 pub fn syncing_feed_count(&self) -> usize {184 pub fn syncing_feed_count(&self) -> usize {
@@ -236,11 +243,6 @@ impl SyncTaskManager {
236 );243 );
237 }244 }
238 SyncMessage::SyncProgress { feed_id, progress, message } => {245 SyncMessage::SyncProgress { feed_id, progress, message } => {
239- if let Some(status) = self.status.feed_sync_status.get_mut(feed_id) {
240- if let FeedSyncStatus::InProgress { progress: _, message: _ } = status {
241- // Update existing in-progress status
242- }
243- }
244 self.status.update_feed_status(246 self.status.update_feed_status(
245 feed_id.clone(),247 feed_id.clone(),
246 FeedSyncStatus::InProgress {248 FeedSyncStatus::InProgress {
@@ -257,6 +259,16 @@ impl SyncTaskManager {
257 episodes_downloaded: *episodes_downloaded,259 episodes_downloaded: *episodes_downloaded,
258 }260 }
259 );261 );
262+
263+ // Check if all feeds are completed
264+ if self.status.global_sync_in_progress {
265+ let all_completed = self.status.feed_sync_status.values().all(|s| {
266+ matches!(s, FeedSyncStatus::Completed { .. } | FeedSyncStatus::Failed { .. })
267+ });
268+ if all_completed {
269+ self.status.complete_global_sync();
270+ }
271+ }
260 }272 }
261 SyncMessage::SyncFailed { feed_id, feed_name: _, error } => {273 SyncMessage::SyncFailed { feed_id, feed_name: _, error } => {
262 self.status.update_feed_status(274 self.status.update_feed_status(
@@ -265,6 +277,20 @@ impl SyncTaskManager {
265 error: error.clone(),277 error: error.clone(),
266 }278 }
267 );279 );
280+
281+ // Check if all feeds are completed (including failed ones)
282+ if self.status.global_sync_in_progress {
283+ let all_completed = self.status.feed_sync_status.values().all(|s| {
284+ matches!(s, FeedSyncStatus::Completed { .. } | FeedSyncStatus::Failed { .. })
285+ });
286+ if all_completed {
287+ self.status.complete_global_sync();
288+ }
289+ }
290+
291+ // For single feed sync, we should also clean up after a failure
292+ // Remove the feed from tracking after a reasonable delay
293+ // This is handled by the is_syncing() check which looks at InProgress status
268 }294 }
269 SyncMessage::EpisodeDownloadStarted { feed_id: _, episode_title: _ } => {295 SyncMessage::EpisodeDownloadStarted { feed_id: _, episode_title: _ } => {
270 // Episode download started - could track this if needed296 // Episode download started - could track this if needed
@@ -310,6 +336,13 @@ impl SyncTaskManager {
310 crate::rss::async_sync::sync_feed_async(feed, sender);336 crate::rss::async_sync::sync_feed_async(feed, sender);
311 });337 });
312 }338 }
339+
340+ /// Clears completed and failed sync statuses
341+ pub fn clear_completed_sync_status(&mut self) {
342+ self.status.feed_sync_status.retain(|_, status| {
343+ matches!(status, FeedSyncStatus::InProgress { .. })
344+ });
345+ }
313 }346 }
314 347
315 impl Default for SyncTaskManager {348 impl Default for SyncTaskManager {
modified src/ui/home.rs +0 -3
@@ -33,9 +33,6 @@ pub fn draw_home(state: &mut AppState, ui: &mut Ui) {
3333 draw_action_buttons(state, ui);
3434 });
3535
36- // Show sync status panel
37- crate::ui::sync_status::draw_sync_status_panel(state, ui);
38-
3936 ui.add_space(16.0);
4037 ui.separator();
4138
@@ -33,9 +33,6 @@ pub fn draw_home(state: &mut AppState, ui: &mut Ui) {
33 draw_action_buttons(state, ui);33 draw_action_buttons(state, ui);
34 });34 });
35 35
36- // Show sync status panel
37- crate::ui::sync_status::draw_sync_status_panel(state, ui);
38-
39 ui.add_space(16.0);36 ui.add_space(16.0);
40 ui.separator();37 ui.separator();
41 38
modified src/ui/status_bar.rs +65 -66
@@ -29,80 +29,79 @@ pub fn draw_status_bar(state: &mut AppState, ui: &mut Ui) {
2929 ..Default::default()
3030 }
3131 .show(ui, |ui| {
32- ui.spacing_mut().item_spacing.x = 16.0;
32+ ui.spacing_mut().item_spacing.x = 16.0;
3333
34- // Error message (highest priority) avec icône
35- if let Some(ref error) = state.error_message {
36- let error_clone = error.clone();
37- ui.horizontal(|ui| {
38- ui.label(egui::RichText::new("❌ ").color(egui::Color32::RED));
39- ui.colored_label(egui::Color32::from_rgb(240, 100, 100), &error_clone);
40- if ui.button(egui::RichText::new("✕").size(14.0)).clicked() {
41- state.clear_error();
42- }
43- });
44- return;
45- }
34+ // Error message (highest priority) avec icône
35+ if let Some(ref error) = state.error_message {
36+ let error_clone = error.clone();
37+ ui.horizontal(|ui| {
38+ ui.label(egui::RichText::new("❌ ").color(egui::Color32::RED));
39+ ui.colored_label(egui::Color32::from_rgb(240, 100, 100), &error_clone);
40+ if ui.button(egui::RichText::new("✕").size(14.0)).clicked() {
41+ state.clear_error();
42+ }
43+ });
44+ return;
45+ }
4646
47- // Info message avec icône
48- if let Some(ref info) = state.info_message {
49- let info_clone = info.clone();
50- ui.horizontal(|ui| {
51- ui.label(egui::RichText::new("ℹ️ ").color(egui::Color32::from_rgb(100, 200, 100)));
52- ui.colored_label(egui::Color32::from_rgb(100, 220, 100), &info_clone);
53- if ui.button(egui::RichText::new("✕").size(14.0)).clicked() {
54- state.clear_info();
55- }
56- });
57- return;
58- }
47+ // Info message avec icône
48+ if let Some(ref info) = state.info_message {
49+ let info_clone = info.clone();
50+ ui.horizontal(|ui| {
51+ ui.label(egui::RichText::new("ℹ️ ").color(egui::Color32::from_rgb(100, 200, 100)));
52+ ui.colored_label(egui::Color32::from_rgb(100, 220, 100), &info_clone);
53+ if ui.button(egui::RichText::new("✕").size(14.0)).clicked() {
54+ state.clear_info();
55+ }
56+ });
57+ return;
58+ }
5959
60- // Sync status avec icône
61- let feed_count = state.feed_count();
62- let enabled_count = state.enabled_feed_count();
60+ // Sync status avec icône
61+ let feed_count = state.feed_count();
62+ let enabled_count = state.enabled_feed_count();
63+
64+ // Process sync messages
65+ state.sync_task_manager.process_messages();
66+
67+ ui.horizontal(|ui| {
68+ // Use the sync indicator from sync_status module
69+ crate::ui::sync_status::draw_sync_indicator(state, ui);
6370
64- // Process sync messages and get status
65- let is_syncing = state.sync_task_manager.status.is_syncing();
66- let global_sync_in_progress = state.sync_task_manager.status.global_sync_in_progress;
71+ // Show ready status only if no sync activity at all
72+ if !state.sync_task_manager.status.has_sync_activity() {
73+ ui.label(egui::RichText::new("✅ ").color(egui::Color32::from_rgb(100, 220, 100)));
74+ ui.colored_label(egui::Color32::from_rgb(150, 200, 150), "Prêt");
75+ }
76+
77+ // Theme toggle button
78+ let theme_icon = match state.get_theme() {
79+ Theme::Dark => "🌙",
80+ Theme::Light => "☀️",
81+ };
82+ let theme_tooltip = match state.get_theme() {
83+ Theme::Dark => "Basculer vers le thème clair",
84+ Theme::Light => "Basculer vers le thème sombre",
85+ };
6786
68- ui.horizontal(|ui| {
69- // Use the sync indicator from sync_status module
70- crate::ui::sync_status::draw_sync_indicator(state, ui);
71-
72- // Show ready status if no sync in progress
73- if !is_syncing && !global_sync_in_progress {
74- ui.label(egui::RichText::new("✅ ").color(egui::Color32::from_rgb(100, 220, 100)));
75- ui.colored_label(egui::Color32::from_rgb(150, 200, 150), "Prêt");
76- }
87+ if ui.link(egui::RichText::new(theme_icon).size(16.0))
88+ .on_hover_text(theme_tooltip)
89+ .clicked()
90+ {
91+ state.toggle_theme();
92+ }
7793
78- // Theme toggle button
79- let theme_icon = match state.get_theme() {
80- Theme::Dark => "🌙",
81- Theme::Light => "☀️",
82- };
83- let theme_tooltip = match state.get_theme() {
84- Theme::Dark => "Basculer vers le thème clair",
85- Theme::Light => "Basculer vers le thème sombre",
86- };
94+ // Feed count on the right avec icône
95+ ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
96+ ui.label(egui::RichText::new(format!("📊 {} flux ", feed_count))
97+ .color(egui::Color32::from_rgb(160, 180, 200)));
8798
88- if ui.link(egui::RichText::new(theme_icon).size(16.0))
89- .on_hover_text(theme_tooltip)
90- .clicked()
91- {
92- state.toggle_theme();
99+ // Nombre de flux activés
100+ if enabled_count < feed_count {
101+ ui.label(egui::RichText::new(format!("| {} activés", enabled_count))
102+ .color(egui::Color32::from_rgb(140, 160, 180)));
93103 }
94-
95- // Feed count on the right avec icône
96- ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
97- ui.label(egui::RichText::new(format!("📊 {} flux ", feed_count))
98- .color(egui::Color32::from_rgb(160, 180, 200)));
99-
100- // Nombre de flux activés
101- if enabled_count < feed_count {
102- ui.label(egui::RichText::new(format!("| {} activés", enabled_count))
103- .color(egui::Color32::from_rgb(140, 160, 180)));
104- }
105- });
106104 });
107105 });
106+ });
108107 }
@@ -29,80 +29,79 @@ pub fn draw_status_bar(state: &mut AppState, ui: &mut Ui) {
29 ..Default::default()29 ..Default::default()
30 }30 }
31 .show(ui, |ui| {31 .show(ui, |ui| {
32- ui.spacing_mut().item_spacing.x = 16.0;32+ ui.spacing_mut().item_spacing.x = 16.0;
33 33
34- // Error message (highest priority) avec icône34+ // Error message (highest priority) avec icône
35- if let Some(ref error) = state.error_message {35+ if let Some(ref error) = state.error_message {
36- let error_clone = error.clone();36+ let error_clone = error.clone();
37- ui.horizontal(|ui| {37+ ui.horizontal(|ui| {
38- ui.label(egui::RichText::new("❌ ").color(egui::Color32::RED));38+ ui.label(egui::RichText::new("❌ ").color(egui::Color32::RED));
39- ui.colored_label(egui::Color32::from_rgb(240, 100, 100), &error_clone);39+ ui.colored_label(egui::Color32::from_rgb(240, 100, 100), &error_clone);
40- if ui.button(egui::RichText::new("✕").size(14.0)).clicked() {40+ if ui.button(egui::RichText::new("✕").size(14.0)).clicked() {
41- state.clear_error();41+ state.clear_error();
42- }42+ }
43- });43+ });
44- return;44+ return;
45- }45+ }
46 46
47- // Info message avec icône47+ // Info message avec icône
48- if let Some(ref info) = state.info_message {48+ if let Some(ref info) = state.info_message {
49- let info_clone = info.clone();49+ let info_clone = info.clone();
50- ui.horizontal(|ui| {50+ ui.horizontal(|ui| {
51- ui.label(egui::RichText::new("ℹ️ ").color(egui::Color32::from_rgb(100, 200, 100)));51+ ui.label(egui::RichText::new("ℹ️ ").color(egui::Color32::from_rgb(100, 200, 100)));
52- ui.colored_label(egui::Color32::from_rgb(100, 220, 100), &info_clone);52+ ui.colored_label(egui::Color32::from_rgb(100, 220, 100), &info_clone);
53- if ui.button(egui::RichText::new("✕").size(14.0)).clicked() {53+ if ui.button(egui::RichText::new("✕").size(14.0)).clicked() {
54- state.clear_info();54+ state.clear_info();
55- }55+ }
56- });56+ });
57- return;57+ return;
58- }58+ }
59 59
60- // Sync status avec icône60+ // Sync status avec icône
61- let feed_count = state.feed_count();61+ let feed_count = state.feed_count();
62- let enabled_count = state.enabled_feed_count();62+ let enabled_count = state.enabled_feed_count();
63+
64+ // Process sync messages
65+ state.sync_task_manager.process_messages();
66+
67+ ui.horizontal(|ui| {
68+ // Use the sync indicator from sync_status module
69+ crate::ui::sync_status::draw_sync_indicator(state, ui);
63 70
64- // Process sync messages and get status71+ // Show ready status only if no sync activity at all
65- let is_syncing = state.sync_task_manager.status.is_syncing();72+ if !state.sync_task_manager.status.has_sync_activity() {
66- let global_sync_in_progress = state.sync_task_manager.status.global_sync_in_progress;73+ ui.label(egui::RichText::new("✅ ").color(egui::Color32::from_rgb(100, 220, 100)));
74+ ui.colored_label(egui::Color32::from_rgb(150, 200, 150), "Prêt");
75+ }
76+
77+ // Theme toggle button
78+ let theme_icon = match state.get_theme() {
79+ Theme::Dark => "🌙",
80+ Theme::Light => "☀️",
81+ };
82+ let theme_tooltip = match state.get_theme() {
83+ Theme::Dark => "Basculer vers le thème clair",
84+ Theme::Light => "Basculer vers le thème sombre",
85+ };
67 86
68- ui.horizontal(|ui| {87+ if ui.link(egui::RichText::new(theme_icon).size(16.0))
69- // Use the sync indicator from sync_status module88+ .on_hover_text(theme_tooltip)
70- crate::ui::sync_status::draw_sync_indicator(state, ui);89+ .clicked()
71- 90+ {
72- // Show ready status if no sync in progress91+ state.toggle_theme();
73- if !is_syncing && !global_sync_in_progress {92+ }
74- ui.label(egui::RichText::new("✅ ").color(egui::Color32::from_rgb(100, 220, 100)));
75- ui.colored_label(egui::Color32::from_rgb(150, 200, 150), "Prêt");
76- }
77 93
78- // Theme toggle button94+ // Feed count on the right avec icône
79- let theme_icon = match state.get_theme() {95+ ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
80- Theme::Dark => "🌙",96+ ui.label(egui::RichText::new(format!("📊 {} flux ", feed_count))
81- Theme::Light => "☀️",97+ .color(egui::Color32::from_rgb(160, 180, 200)));
82- };
83- let theme_tooltip = match state.get_theme() {
84- Theme::Dark => "Basculer vers le thème clair",
85- Theme::Light => "Basculer vers le thème sombre",
86- };
87 98
88- if ui.link(egui::RichText::new(theme_icon).size(16.0))99+ // Nombre de flux activés
89- .on_hover_text(theme_tooltip)100+ if enabled_count < feed_count {
90- .clicked()101+ ui.label(egui::RichText::new(format!("| {} activés", enabled_count))
91- {102+ .color(egui::Color32::from_rgb(140, 160, 180)));
92- state.toggle_theme();
93 }103 }
94-
95- // Feed count on the right avec icône
96- ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
97- ui.label(egui::RichText::new(format!("📊 {} flux ", feed_count))
98- .color(egui::Color32::from_rgb(160, 180, 200)));
99-
100- // Nombre de flux activés
101- if enabled_count < feed_count {
102- ui.label(egui::RichText::new(format!("| {} activés", enabled_count))
103- .color(egui::Color32::from_rgb(140, 160, 180)));
104- }
105- });
106 });104 });
107 });105 });
106+ });
108 }107 }
modified src/ui/sync_status.rs +42 -147
@@ -1,73 +1,9 @@
1-//! Sync Status Panel UI component
1+//! Sync Status UI component
22 //!
3-//! This module displays the synchronization status and progress.
3+//! This module displays the synchronization status and progress in the status bar.
44
55 use crate::config::AppState;
6-use crate::config::sync_task::FeedSyncStatus;
7-use egui::{Color32, Frame, RichText, Ui};
8-
9-/// Draws the sync status panel
10-pub fn draw_sync_status_panel(state: &mut AppState, ui: &mut Ui) {
11- let status = &state.sync_task_manager.status;
12-
13- // Only show panel if there's sync activity
14- if !status.is_syncing() && !status.global_sync_in_progress {
15- return;
16- }
17-
18- // Create a frame for the sync status panel
19- Frame {
20- fill: Color32::from_rgb(40, 42, 54),
21- stroke: egui::Stroke::new(1.0, Color32::from_rgb(130, 170, 250)),
22- corner_radius: 8.0.into(),
23- inner_margin: egui::Margin::symmetric(12, 8),
24- ..Default::default()
25- }
26- .show(ui, |ui| {
27- ui.vertical(|ui| {
28- // Header
29- ui.horizontal(|ui| {
30- if status.global_sync_in_progress {
31- ui.label(RichText::new("🔄 Synchronisation en cours").size(16.0).strong());
32- } else {
33- ui.label(RichText::new("📊 Statut de synchronisation").size(16.0).strong());
34- }
35-
36- // Overall progress bar for global sync
37- if status.global_sync_in_progress {
38- ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
39- draw_progress_bar(ui, status.get_overall_progress(), 120.0);
40- ui.label(RichText::new(format!("{:.0}%", status.get_overall_progress() * 100.0))
41- .size(14.0)
42- .color(Color32::from_rgb(220, 230, 245)));
43- });
44- }
45- });
46-
47- ui.separator();
48- ui.add_space(8.0);
49-
50- // Global progress info
51- if status.global_sync_in_progress {
52- ui.label(RichText::new(format!(
53- "Synchronisation de {} flux: {}/{} terminés",
54- status.total_feeds,
55- status.completed_feeds,
56- status.total_feeds
57- )).size(14.0).color(Color32::from_rgb(200, 210, 220)));
58- ui.add_space(8.0);
59- }
60-
61- // Individual feed status
62- ui.label(RichText::new("Statut par flux:").size(14.0).strong());
63- ui.add_space(4.0);
64-
65- for (feed_id, feed_status) in &status.feed_sync_status {
66- draw_feed_sync_status(ui, feed_id, feed_status);
67- }
68- });
69- });
70-}
6+use egui::{Color32, RichText, Ui};
717
728 /// Draws a progress bar
739 fn draw_progress_bar(ui: &mut Ui, progress: f32, width: f32) {
@@ -110,104 +46,63 @@ fn draw_progress_bar(ui: &mut Ui, progress: f32, width: f32) {
11046 ui.advance_cursor_after_rect(bg_rect);
11147 }
11248
113-/// Draws the sync status for a single feed
114-fn draw_feed_sync_status(ui: &mut Ui, feed_id: &str, status: &FeedSyncStatus) {
115- ui.horizontal(|ui| {
116- ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 4.0);
117-
118- // Feed ID/name (truncated if too long)
119- let display_id = if feed_id.len() > 20 {
120- format!("{}...", &feed_id[..17])
121- } else {
122- feed_id.to_string()
123- };
124-
125- match status {
126- FeedSyncStatus::Idle => {
127- ui.label(RichText::new(format!("⏸️ {}", display_id))
128- .size(13.0)
129- .color(Color32::from_rgb(150, 160, 180)));
130- }
131- FeedSyncStatus::InProgress { progress, message } => {
132- ui.label(RichText::new(format!("🔄 {}", display_id))
133- .size(13.0)
134- .color(Color32::from_rgb(180, 200, 255)));
135-
136- // Progress bar
137- draw_progress_bar(ui, *progress, 80.0);
138- ui.add_space(4.0);
139-
140- // Status message
141- ui.label(RichText::new(format!("{}", message))
142- .size(12.0)
143- .color(Color32::from_rgb(180, 190, 210)));
144- }
145- FeedSyncStatus::Completed { episodes_found, episodes_downloaded } => {
146- ui.label(RichText::new(format!("✅ {}", display_id))
147- .size(13.0)
148- .color(Color32::from_rgb(80, 220, 100)));
149-
150- ui.label(RichText::new(format!("{} épisodes, {} téléchargés", episodes_found, episodes_downloaded))
151- .size(12.0)
152- .color(Color32::from_rgb(160, 180, 200)));
153- }
154- FeedSyncStatus::Failed { error } => {
155- ui.label(RichText::new(format!("❌ {}", display_id))
156- .size(13.0)
157- .color(Color32::from_rgb(220, 80, 80)));
158-
159- ui.label(RichText::new(format!("Erreur: {}", error))
160- .size(12.0)
161- .color(Color32::from_rgb(200, 100, 100)));
162- }
163- }
164- });
165-
166- ui.add_space(4.0);
167-}
168-
16949 /// Draws a compact sync indicator for the status bar
170-pub fn draw_sync_indicator(state: &AppState, ui: &mut Ui) {
50+pub fn draw_sync_indicator(state: &mut AppState, ui: &mut Ui) {
51+ // Process messages first
52+ state.sync_task_manager.process_messages();
53+
17154 let status = &state.sync_task_manager.status;
17255
173- if !status.is_syncing() {
56+ // Always show sync info if there's any activity
57+ if !status.has_sync_activity() {
17458 return;
17559 }
17660
61+ let global_sync_in_progress = status.global_sync_in_progress;
62+ let syncing_count = status.syncing_feed_count();
63+ let completed_count = status.feed_sync_status.values()
64+ .filter(|s| matches!(s, crate::config::sync_task::FeedSyncStatus::Completed { .. }))
65+ .count();
66+ let failed_count = status.feed_sync_status.values()
67+ .filter(|s| matches!(s, crate::config::sync_task::FeedSyncStatus::Failed { .. }))
68+ .count();
69+ let total_feeds = status.total_feeds;
70+ let completed_feeds = status.completed_feeds;
71+ let overall_progress = status.get_overall_progress();
72+
17773 ui.horizontal(|ui| {
17874 ui.spacing_mut().item_spacing = egui::Vec2::new(4.0, 0.0);
17975
180- if status.global_sync_in_progress {
76+ if global_sync_in_progress {
18177 // Global sync indicator
18278 ui.label(RichText::new("🔄").size(16.0));
18379 ui.label(RichText::new(format!("Synchro: {}/{} flux",
184- status.completed_feeds,
185- status.total_feeds))
80+ completed_feeds,
81+ total_feeds))
18682 .size(13.0)
18783 .color(Color32::from_rgb(180, 200, 255)));
18884
18985 // Overall progress bar
190- draw_progress_bar(ui, status.get_overall_progress(), 60.0);
191- } else {
86+ draw_progress_bar(ui, overall_progress, 60.0);
87+ } else if syncing_count > 0 {
19288 // Individual feed sync indicator
193- let syncing_count = status.syncing_feed_count();
194- if syncing_count > 0 {
195- ui.label(RichText::new("🔄").size(16.0));
196- ui.label(RichText::new(format!("{} flux en synchro", syncing_count))
197- .size(13.0)
198- .color(Color32::from_rgb(180, 200, 255)));
89+ ui.label(RichText::new("🔄").size(16.0));
90+ ui.label(RichText::new(format!("{} flux en synchro", syncing_count))
91+ .size(13.0)
92+ .color(Color32::from_rgb(180, 200, 255)));
93+ } else if completed_count > 0 || failed_count > 0 {
94+ // Show completion status briefly
95+ ui.label(RichText::new("✅").size(16.0).color(Color32::from_rgb(80, 220, 100)));
96+ let total_completed = completed_count + failed_count;
97+ ui.label(RichText::new(format!("Synchro terminée: {} flux ({} OK, {} erreur)",
98+ total_completed, completed_count, failed_count))
99+ .size(13.0)
100+ .color(Color32::from_rgb(150, 220, 150)));
101+
102+ // Add a button to clear completed sync status
103+ if ui.button(RichText::new("✕").size(12.0)).clicked() {
104+ state.sync_task_manager.clear_completed_sync_status();
199105 }
200106 }
201107 });
202108 }
203-
204-#[cfg(test)]
205-mod tests {
206- use super::*;
207-
208- #[test]
209- fn test_draw_progress_bar() {
210- // This is a UI test, can't easily test rendering
211- // Just verify the function doesn't panic
212- }
213-}
@@ -1,73 +1,9 @@
1-//! Sync Status Panel UI component1+//! Sync Status UI component
2 //!2 //!
3-//! This module displays the synchronization status and progress.3+//! This module displays the synchronization status and progress in the status bar.
4 4
5 use crate::config::AppState;5 use crate::config::AppState;
6-use crate::config::sync_task::FeedSyncStatus;6+use egui::{Color32, RichText, Ui};
7-use egui::{Color32, Frame, RichText, Ui};
8-
9-/// Draws the sync status panel
10-pub fn draw_sync_status_panel(state: &mut AppState, ui: &mut Ui) {
11- let status = &state.sync_task_manager.status;
12-
13- // Only show panel if there's sync activity
14- if !status.is_syncing() && !status.global_sync_in_progress {
15- return;
16- }
17-
18- // Create a frame for the sync status panel
19- Frame {
20- fill: Color32::from_rgb(40, 42, 54),
21- stroke: egui::Stroke::new(1.0, Color32::from_rgb(130, 170, 250)),
22- corner_radius: 8.0.into(),
23- inner_margin: egui::Margin::symmetric(12, 8),
24- ..Default::default()
25- }
26- .show(ui, |ui| {
27- ui.vertical(|ui| {
28- // Header
29- ui.horizontal(|ui| {
30- if status.global_sync_in_progress {
31- ui.label(RichText::new("🔄 Synchronisation en cours").size(16.0).strong());
32- } else {
33- ui.label(RichText::new("📊 Statut de synchronisation").size(16.0).strong());
34- }
35-
36- // Overall progress bar for global sync
37- if status.global_sync_in_progress {
38- ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
39- draw_progress_bar(ui, status.get_overall_progress(), 120.0);
40- ui.label(RichText::new(format!("{:.0}%", status.get_overall_progress() * 100.0))
41- .size(14.0)
42- .color(Color32::from_rgb(220, 230, 245)));
43- });
44- }
45- });
46-
47- ui.separator();
48- ui.add_space(8.0);
49-
50- // Global progress info
51- if status.global_sync_in_progress {
52- ui.label(RichText::new(format!(
53- "Synchronisation de {} flux: {}/{} terminés",
54- status.total_feeds,
55- status.completed_feeds,
56- status.total_feeds
57- )).size(14.0).color(Color32::from_rgb(200, 210, 220)));
58- ui.add_space(8.0);
59- }
60-
61- // Individual feed status
62- ui.label(RichText::new("Statut par flux:").size(14.0).strong());
63- ui.add_space(4.0);
64-
65- for (feed_id, feed_status) in &status.feed_sync_status {
66- draw_feed_sync_status(ui, feed_id, feed_status);
67- }
68- });
69- });
70-}
71 7
72 /// Draws a progress bar8 /// Draws a progress bar
73 fn draw_progress_bar(ui: &mut Ui, progress: f32, width: f32) {9 fn draw_progress_bar(ui: &mut Ui, progress: f32, width: f32) {
@@ -110,104 +46,63 @@ fn draw_progress_bar(ui: &mut Ui, progress: f32, width: f32) {
110 ui.advance_cursor_after_rect(bg_rect);46 ui.advance_cursor_after_rect(bg_rect);
111 }47 }
112 48
113-/// Draws the sync status for a single feed
114-fn draw_feed_sync_status(ui: &mut Ui, feed_id: &str, status: &FeedSyncStatus) {
115- ui.horizontal(|ui| {
116- ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 4.0);
117-
118- // Feed ID/name (truncated if too long)
119- let display_id = if feed_id.len() > 20 {
120- format!("{}...", &feed_id[..17])
121- } else {
122- feed_id.to_string()
123- };
124-
125- match status {
126- FeedSyncStatus::Idle => {
127- ui.label(RichText::new(format!("⏸️ {}", display_id))
128- .size(13.0)
129- .color(Color32::from_rgb(150, 160, 180)));
130- }
131- FeedSyncStatus::InProgress { progress, message } => {
132- ui.label(RichText::new(format!("🔄 {}", display_id))
133- .size(13.0)
134- .color(Color32::from_rgb(180, 200, 255)));
135-
136- // Progress bar
137- draw_progress_bar(ui, *progress, 80.0);
138- ui.add_space(4.0);
139-
140- // Status message
141- ui.label(RichText::new(format!("{}", message))
142- .size(12.0)
143- .color(Color32::from_rgb(180, 190, 210)));
144- }
145- FeedSyncStatus::Completed { episodes_found, episodes_downloaded } => {
146- ui.label(RichText::new(format!("✅ {}", display_id))
147- .size(13.0)
148- .color(Color32::from_rgb(80, 220, 100)));
149-
150- ui.label(RichText::new(format!("{} épisodes, {} téléchargés", episodes_found, episodes_downloaded))
151- .size(12.0)
152- .color(Color32::from_rgb(160, 180, 200)));
153- }
154- FeedSyncStatus::Failed { error } => {
155- ui.label(RichText::new(format!("❌ {}", display_id))
156- .size(13.0)
157- .color(Color32::from_rgb(220, 80, 80)));
158-
159- ui.label(RichText::new(format!("Erreur: {}", error))
160- .size(12.0)
161- .color(Color32::from_rgb(200, 100, 100)));
162- }
163- }
164- });
165-
166- ui.add_space(4.0);
167-}
168-
169 /// Draws a compact sync indicator for the status bar49 /// Draws a compact sync indicator for the status bar
170-pub fn draw_sync_indicator(state: &AppState, ui: &mut Ui) {50+pub fn draw_sync_indicator(state: &mut AppState, ui: &mut Ui) {
51+ // Process messages first
52+ state.sync_task_manager.process_messages();
53+
171 let status = &state.sync_task_manager.status;54 let status = &state.sync_task_manager.status;
172 55
173- if !status.is_syncing() {56+ // Always show sync info if there's any activity
57+ if !status.has_sync_activity() {
174 return;58 return;
175 }59 }
176 60
61+ let global_sync_in_progress = status.global_sync_in_progress;
62+ let syncing_count = status.syncing_feed_count();
63+ let completed_count = status.feed_sync_status.values()
64+ .filter(|s| matches!(s, crate::config::sync_task::FeedSyncStatus::Completed { .. }))
65+ .count();
66+ let failed_count = status.feed_sync_status.values()
67+ .filter(|s| matches!(s, crate::config::sync_task::FeedSyncStatus::Failed { .. }))
68+ .count();
69+ let total_feeds = status.total_feeds;
70+ let completed_feeds = status.completed_feeds;
71+ let overall_progress = status.get_overall_progress();
72+
177 ui.horizontal(|ui| {73 ui.horizontal(|ui| {
178 ui.spacing_mut().item_spacing = egui::Vec2::new(4.0, 0.0);74 ui.spacing_mut().item_spacing = egui::Vec2::new(4.0, 0.0);
179 75
180- if status.global_sync_in_progress {76+ if global_sync_in_progress {
181 // Global sync indicator77 // Global sync indicator
182 ui.label(RichText::new("🔄").size(16.0));78 ui.label(RichText::new("🔄").size(16.0));
183 ui.label(RichText::new(format!("Synchro: {}/{} flux", 79 ui.label(RichText::new(format!("Synchro: {}/{} flux",
184- status.completed_feeds, 80+ completed_feeds,
185- status.total_feeds))81+ total_feeds))
186 .size(13.0)82 .size(13.0)
187 .color(Color32::from_rgb(180, 200, 255)));83 .color(Color32::from_rgb(180, 200, 255)));
188 84
189 // Overall progress bar85 // Overall progress bar
190- draw_progress_bar(ui, status.get_overall_progress(), 60.0);86+ draw_progress_bar(ui, overall_progress, 60.0);
191- } else {87+ } else if syncing_count > 0 {
192 // Individual feed sync indicator88 // Individual feed sync indicator
193- let syncing_count = status.syncing_feed_count();89+ ui.label(RichText::new("🔄").size(16.0));
194- if syncing_count > 0 {90+ ui.label(RichText::new(format!("{} flux en synchro", syncing_count))
195- ui.label(RichText::new("🔄").size(16.0));91+ .size(13.0)
196- ui.label(RichText::new(format!("{} flux en synchro", syncing_count))92+ .color(Color32::from_rgb(180, 200, 255)));
197- .size(13.0)93+ } else if completed_count > 0 || failed_count > 0 {
198- .color(Color32::from_rgb(180, 200, 255)));94+ // Show completion status briefly
95+ ui.label(RichText::new("✅").size(16.0).color(Color32::from_rgb(80, 220, 100)));
96+ let total_completed = completed_count + failed_count;
97+ ui.label(RichText::new(format!("Synchro terminée: {} flux ({} OK, {} erreur)",
98+ total_completed, completed_count, failed_count))
99+ .size(13.0)
100+ .color(Color32::from_rgb(150, 220, 150)));
101+
102+ // Add a button to clear completed sync status
103+ if ui.button(RichText::new("✕").size(12.0)).clicked() {
104+ state.sync_task_manager.clear_completed_sync_status();
199 }105 }
200 }106 }
201 });107 });
202 }108 }
203-
204-#[cfg(test)]
205-mod tests {
206- use super::*;
207-
208- #[test]
209- fn test_draw_progress_bar() {
210- // This is a UI test, can't easily test rendering
211- // Just verify the function doesn't panic
212- }
213-}