Correction des erreurs de compilation et mise à jour des fichiers de suivi
a0ba2e9 parent: 36df17d modified
spec/init-plan.md +1 -1 | @@ -340,7 +340,7 @@ pub async fn download_episode( | ||
| 340 | 340 | | Phase 6 | 2 jours | J13-J14 | ✅ | |
| 341 | 341 | | Phase 7 | 1 jour | J15 | ✅ | |
| 342 | 342 | | Phase 8 | 3 jours | J16-J18 | ✅ | |
| 343 | -| Phase 9 | 2 jours | J19-J20 | ⬜ | | |
| 343 | +| Phase 9 | 2 jours | J19-J20 | ✅ | | |
| 344 | 344 | | **Total** | **20 jours** | | | |
| 345 | 345 | |
| 346 | 346 | --- |
| @@ -340,7 +340,7 @@ pub async fn download_episode( | |||
| 340 | | Phase 6 | 2 jours | J13-J14 | ✅ | | 340 | | Phase 6 | 2 jours | J13-J14 | ✅ | |
| 341 | | Phase 7 | 1 jour | J15 | ✅ | | 341 | | Phase 7 | 1 jour | J15 | ✅ | |
| 342 | | Phase 8 | 3 jours | J16-J18 | ✅ | | 342 | | Phase 8 | 3 jours | J16-J18 | ✅ | |
| 343 | -| Phase 9 | 2 jours | J19-J20 | ⬜ | | 343 | +| Phase 9 | 2 jours | J19-J20 | ✅ | |
| 344 | | **Total** | **20 jours** | | | | 344 | | **Total** | **20 jours** | | | |
| 345 | 345 | ||
| 346 | --- | 346 | --- |
modified
spec/init-tasks.md +23 -23 | @@ -199,27 +199,27 @@ | ||
| 199 | 199 | |
| 200 | 200 | | ID | Tâche | Priorité | Durée | Fichiers | Dépendances | Statut | |
| 201 | 201 | |----|-------|----------|-------|---------|-------------|--------| |
| 202 | -| T9.1 | Créer `config/search.rs` avec SearchQuery et SearchResult | H | 1h | `src/config/search.rs` | T2.1, T2.3 | ⬜ | | |
| 203 | -| T9.2 | Implémenter la fonction de recherche dans les flux et épisodes | H | 2h | `src/config/search.rs` | T9.1 | ⬜ | | |
| 204 | -| T9.3 | Créer `ui/search_bar.rs` avec la barre de recherche UI | H | 2h | `src/ui/search_bar.rs` | T9.2 | ⬜ | | |
| 205 | -| T9.4 | Intégrer la recherche dans l'application | H | 1h | `src/app.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.3 | ⬜ | | |
| 206 | -| T9.5 | Ajouter les types de tri dans `config/app_state.rs` | H | 1h | `src/config/app_state.rs` | T2.4 | ⬜ | | |
| 207 | -| T9.6 | Implémenter la logique de tri dans `ui/feed_list.rs` | H | 2h | `src/ui/feed_list.rs` | T9.5 | ⬜ | | |
| 208 | -| T9.7 | Ajouter les contrôles de tri dans l'UI | M | 1h | `src/ui/feed_list.rs`, `src/ui/home.rs` | T9.6 | ⬜ | | |
| 209 | -| T9.8 | Ajouter la crate `mp3-metadata` à Cargo.toml | M | 0.5h | `Cargo.toml` | T1.2 | ⬜ | | |
| 210 | -| T9.9 | Créer `rss/audio_metadata.rs` avec la lecture des métadonnées | M | 2h | `src/rss/audio_metadata.rs` | T9.8 | ⬜ | | |
| 211 | -| T9.10 | Intégrer l'affichage de la durée dans `ui/feed_list.rs` | M | 1h | `src/ui/feed_list.rs`, `src/rss/mod.rs` | T9.9 | ⬜ | | |
| 212 | -| T9.11 | Créer `config/history.rs` avec HistoryEntry | M | 1h | `src/config/history.rs` | T2.1 | ⬜ | | |
| 213 | -| T9.12 | Implémenter le logging des opérations | M | 1h | `src/config/history.rs` | T9.11 | ⬜ | | |
| 214 | -| T9.13 | Ajouter l'affichage de l'historique dans l'UI | M | 2h | `src/ui/history_panel.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.12 | ⬜ | | |
| 215 | -| T9.14 | Modifier `rss/downloader.rs` pour supporter la reprise | M | 2h | `src/rss/downloader.rs` | T3.7 | ⬜ | | |
| 216 | -| T9.15 | Ajouter le suivi de la progression partielle | M | 1h | `src/rss/downloader.rs` | T9.14 | ⬜ | | |
| 217 | -| T9.16 | Ajouter le support des raccourcis clavier dans egui | L | 1h | `src/app.rs` | T4.1 | ⬜ | | |
| 218 | -| T9.17 | Implémenter les raccourcis (Ctrl+F, Ctrl+R, Ctrl+S) | L | 2h | `src/app.rs`, `src/ui/*` | T9.16 | ⬜ | | |
| 219 | -| T9.18 | Créer `config/backup.rs` avec backup automatique | L | 1h | `src/config/backup.rs` | T2.8 | ⬜ | | |
| 220 | -| T9.19 | Intégrer le backup dans le cycle de vie de l'app | L | 1h | `src/app.rs`, `src/main.rs` | T9.18 | ⬜ | | |
| 221 | -| T9.20 | Créer `config/statistics.rs` avec Stats globales | L | 1h | `src/config/statistics.rs` | T2.1 | ⬜ | | |
| 222 | -| T9.21 | Ajouter l'affichage des stats dans l'UI | L | 1h | `src/ui/stats_panel.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.20 | ⬜ | | |
| 202 | +| T9.1 | Créer `config/search.rs` avec SearchQuery et SearchResult | H | 1h | `src/config/search.rs` | T2.1, T2.3 | ✅ | | |
| 203 | +| T9.2 | Implémenter la fonction de recherche dans les flux et épisodes | H | 2h | `src/config/search.rs` | T9.1 | ✅ | | |
| 204 | +| T9.3 | Créer `ui/search_bar.rs` avec la barre de recherche UI | H | 2h | `src/ui/search_bar.rs` | T9.2 | ✅ | | |
| 205 | +| T9.4 | Intégrer la recherche dans l'application | H | 1h | `src/app.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.3 | ✅ | | |
| 206 | +| T9.5 | Ajouter les types de tri dans `config/app_state.rs` | H | 1h | `src/config/app_state.rs` | T2.4 | ✅ | | |
| 207 | +| T9.6 | Implémenter la logique de tri dans `ui/feed_list.rs` | H | 2h | `src/ui/feed_list.rs` | T9.5 | ✅ | | |
| 208 | +| T9.7 | Ajouter les contrôles de tri dans l'UI | M | 1h | `src/ui/feed_list.rs`, `src/ui/home.rs` | T9.6 | ✅ | | |
| 209 | +| T9.8 | Ajouter la crate `mp3-metadata` à Cargo.toml | M | 0.5h | `Cargo.toml` | T1.2 | ✅ | | |
| 210 | +| T9.9 | Créer `rss/audio_metadata.rs` avec la lecture des métadonnées | M | 2h | `src/rss/audio_metadata.rs` | T9.8 | ✅ | | |
| 211 | +| T9.10 | Intégrer l'affichage de la durée dans `ui/feed_list.rs` | M | 1h | `src/ui/feed_list.rs`, `src/rss/mod.rs` | T9.9 | ✅ | | |
| 212 | +| T9.11 | Créer `config/history.rs` avec HistoryEntry | M | 1h | `src/config/history.rs` | T2.1 | ✅ | | |
| 213 | +| T9.12 | Implémenter le logging des opérations | M | 1h | `src/config/history.rs` | T9.11 | ✅ | | |
| 214 | +| T9.13 | Ajouter l'affichage de l'historique dans l'UI | M | 2h | `src/ui/history_panel.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.12 | ✅ | | |
| 215 | +| T9.14 | Modifier `rss/downloader.rs` pour supporter la reprise | M | 2h | `src/rss/downloader.rs` | T3.7 | ✅ | | |
| 216 | +| T9.15 | Ajouter le suivi de la progression partielle | M | 1h | `src/rss/downloader.rs` | T9.14 | ✅ | | |
| 217 | +| T9.16 | Ajouter le support des raccourcis clavier dans egui | L | 1h | `src/app.rs` | T4.1 | ✅ | | |
| 218 | +| T9.17 | Implémenter les raccourcis (Ctrl+F, Ctrl+R, Ctrl+S) | L | 2h | `src/app.rs`, `src/ui/*` | T9.16 | ✅ | | |
| 219 | +| T9.18 | Créer `config/backup.rs` avec backup automatique | L | 1h | `src/config/backup.rs` | T2.8 | ✅ | | |
| 220 | +| T9.19 | Intégrer le backup dans le cycle de vie de l'app | L | 1h | `src/app.rs`, `src/main.rs` | T9.18 | ✅ | | |
| 221 | +| T9.20 | Créer `config/statistics.rs` avec Stats globales | L | 1h | `src/config/statistics.rs` | T2.1 | ✅ | | |
| 222 | +| T9.21 | Ajouter l'affichage des stats dans l'UI | L | 1h | `src/ui/stats_panel.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.20 | ✅ | | |
| 223 | 223 | |
| 224 | 224 | **Total Phase 9** : 24 heures (2 jours) |
| 225 | 225 | |
| @@ -237,8 +237,8 @@ | ||
| 237 | 237 | | Phase 6 | 12 tâches | 24h | ✅ Terminé | |
| 238 | 238 | | Phase 7 | 10 tâches | 11h | ✅ Terminé | |
| 239 | 239 | | Phase 8 | 8 tâches | 20h | ✅ Terminé | |
| 240 | -| Phase 9 | 21 tâches | 24h | ⬜ À faire | | |
| 241 | -| **Total** | **109 tâches** | **169 heures** (~21 jours) | ⏳ En cours | | |
| 240 | +| Phase 9 | 21 tâches | 24h | ✅ Terminé | | |
| 241 | +| **Total** | **109 tâches** | **169 heures** (~21 jours) | ✅ Terminé | | |
| 242 | 242 | |
| 243 | 243 | --- |
| 244 | 244 | |
| @@ -199,27 +199,27 @@ | |||
| 199 | 199 | ||
| 200 | | ID | Tâche | Priorité | Durée | Fichiers | Dépendances | Statut | | 200 | | ID | Tâche | Priorité | Durée | Fichiers | Dépendances | Statut | |
| 201 | |----|-------|----------|-------|---------|-------------|--------| | 201 | |----|-------|----------|-------|---------|-------------|--------| |
| 202 | -| T9.1 | Créer `config/search.rs` avec SearchQuery et SearchResult | H | 1h | `src/config/search.rs` | T2.1, T2.3 | ⬜ | | 202 | +| T9.1 | Créer `config/search.rs` avec SearchQuery et SearchResult | H | 1h | `src/config/search.rs` | T2.1, T2.3 | ✅ | |
| 203 | -| T9.2 | Implémenter la fonction de recherche dans les flux et épisodes | H | 2h | `src/config/search.rs` | T9.1 | ⬜ | | 203 | +| T9.2 | Implémenter la fonction de recherche dans les flux et épisodes | H | 2h | `src/config/search.rs` | T9.1 | ✅ | |
| 204 | -| T9.3 | Créer `ui/search_bar.rs` avec la barre de recherche UI | H | 2h | `src/ui/search_bar.rs` | T9.2 | ⬜ | | 204 | +| T9.3 | Créer `ui/search_bar.rs` avec la barre de recherche UI | H | 2h | `src/ui/search_bar.rs` | T9.2 | ✅ | |
| 205 | -| T9.4 | Intégrer la recherche dans l'application | H | 1h | `src/app.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.3 | ⬜ | | 205 | +| T9.4 | Intégrer la recherche dans l'application | H | 1h | `src/app.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.3 | ✅ | |
| 206 | -| T9.5 | Ajouter les types de tri dans `config/app_state.rs` | H | 1h | `src/config/app_state.rs` | T2.4 | ⬜ | | 206 | +| T9.5 | Ajouter les types de tri dans `config/app_state.rs` | H | 1h | `src/config/app_state.rs` | T2.4 | ✅ | |
| 207 | -| T9.6 | Implémenter la logique de tri dans `ui/feed_list.rs` | H | 2h | `src/ui/feed_list.rs` | T9.5 | ⬜ | | 207 | +| T9.6 | Implémenter la logique de tri dans `ui/feed_list.rs` | H | 2h | `src/ui/feed_list.rs` | T9.5 | ✅ | |
| 208 | -| T9.7 | Ajouter les contrôles de tri dans l'UI | M | 1h | `src/ui/feed_list.rs`, `src/ui/home.rs` | T9.6 | ⬜ | | 208 | +| T9.7 | Ajouter les contrôles de tri dans l'UI | M | 1h | `src/ui/feed_list.rs`, `src/ui/home.rs` | T9.6 | ✅ | |
| 209 | -| T9.8 | Ajouter la crate `mp3-metadata` à Cargo.toml | M | 0.5h | `Cargo.toml` | T1.2 | ⬜ | | 209 | +| T9.8 | Ajouter la crate `mp3-metadata` à Cargo.toml | M | 0.5h | `Cargo.toml` | T1.2 | ✅ | |
| 210 | -| T9.9 | Créer `rss/audio_metadata.rs` avec la lecture des métadonnées | M | 2h | `src/rss/audio_metadata.rs` | T9.8 | ⬜ | | 210 | +| T9.9 | Créer `rss/audio_metadata.rs` avec la lecture des métadonnées | M | 2h | `src/rss/audio_metadata.rs` | T9.8 | ✅ | |
| 211 | -| T9.10 | Intégrer l'affichage de la durée dans `ui/feed_list.rs` | M | 1h | `src/ui/feed_list.rs`, `src/rss/mod.rs` | T9.9 | ⬜ | | 211 | +| T9.10 | Intégrer l'affichage de la durée dans `ui/feed_list.rs` | M | 1h | `src/ui/feed_list.rs`, `src/rss/mod.rs` | T9.9 | ✅ | |
| 212 | -| T9.11 | Créer `config/history.rs` avec HistoryEntry | M | 1h | `src/config/history.rs` | T2.1 | ⬜ | | 212 | +| T9.11 | Créer `config/history.rs` avec HistoryEntry | M | 1h | `src/config/history.rs` | T2.1 | ✅ | |
| 213 | -| T9.12 | Implémenter le logging des opérations | M | 1h | `src/config/history.rs` | T9.11 | ⬜ | | 213 | +| T9.12 | Implémenter le logging des opérations | M | 1h | `src/config/history.rs` | T9.11 | ✅ | |
| 214 | -| T9.13 | Ajouter l'affichage de l'historique dans l'UI | M | 2h | `src/ui/history_panel.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.12 | ⬜ | | 214 | +| T9.13 | Ajouter l'affichage de l'historique dans l'UI | M | 2h | `src/ui/history_panel.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.12 | ✅ | |
| 215 | -| T9.14 | Modifier `rss/downloader.rs` pour supporter la reprise | M | 2h | `src/rss/downloader.rs` | T3.7 | ⬜ | | 215 | +| T9.14 | Modifier `rss/downloader.rs` pour supporter la reprise | M | 2h | `src/rss/downloader.rs` | T3.7 | ✅ | |
| 216 | -| T9.15 | Ajouter le suivi de la progression partielle | M | 1h | `src/rss/downloader.rs` | T9.14 | ⬜ | | 216 | +| T9.15 | Ajouter le suivi de la progression partielle | M | 1h | `src/rss/downloader.rs` | T9.14 | ✅ | |
| 217 | -| T9.16 | Ajouter le support des raccourcis clavier dans egui | L | 1h | `src/app.rs` | T4.1 | ⬜ | | 217 | +| T9.16 | Ajouter le support des raccourcis clavier dans egui | L | 1h | `src/app.rs` | T4.1 | ✅ | |
| 218 | -| T9.17 | Implémenter les raccourcis (Ctrl+F, Ctrl+R, Ctrl+S) | L | 2h | `src/app.rs`, `src/ui/*` | T9.16 | ⬜ | | 218 | +| T9.17 | Implémenter les raccourcis (Ctrl+F, Ctrl+R, Ctrl+S) | L | 2h | `src/app.rs`, `src/ui/*` | T9.16 | ✅ | |
| 219 | -| T9.18 | Créer `config/backup.rs` avec backup automatique | L | 1h | `src/config/backup.rs` | T2.8 | ⬜ | | 219 | +| T9.18 | Créer `config/backup.rs` avec backup automatique | L | 1h | `src/config/backup.rs` | T2.8 | ✅ | |
| 220 | -| T9.19 | Intégrer le backup dans le cycle de vie de l'app | L | 1h | `src/app.rs`, `src/main.rs` | T9.18 | ⬜ | | 220 | +| T9.19 | Intégrer le backup dans le cycle de vie de l'app | L | 1h | `src/app.rs`, `src/main.rs` | T9.18 | ✅ | |
| 221 | -| T9.20 | Créer `config/statistics.rs` avec Stats globales | L | 1h | `src/config/statistics.rs` | T2.1 | ⬜ | | 221 | +| T9.20 | Créer `config/statistics.rs` avec Stats globales | L | 1h | `src/config/statistics.rs` | T2.1 | ✅ | |
| 222 | -| T9.21 | Ajouter l'affichage des stats dans l'UI | L | 1h | `src/ui/stats_panel.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.20 | ⬜ | | 222 | +| T9.21 | Ajouter l'affichage des stats dans l'UI | L | 1h | `src/ui/stats_panel.rs`, `src/ui/home.rs`, `src/ui/mod.rs` | T9.20 | ✅ | |
| 223 | 223 | ||
| 224 | **Total Phase 9** : 24 heures (2 jours) | 224 | **Total Phase 9** : 24 heures (2 jours) |
| 225 | 225 | ||
| @@ -237,8 +237,8 @@ | |||
| 237 | | Phase 6 | 12 tâches | 24h | ✅ Terminé | | 237 | | Phase 6 | 12 tâches | 24h | ✅ Terminé | |
| 238 | | Phase 7 | 10 tâches | 11h | ✅ Terminé | | 238 | | Phase 7 | 10 tâches | 11h | ✅ Terminé | |
| 239 | | Phase 8 | 8 tâches | 20h | ✅ Terminé | | 239 | | Phase 8 | 8 tâches | 20h | ✅ Terminé | |
| 240 | -| Phase 9 | 21 tâches | 24h | ⬜ À faire | | 240 | +| Phase 9 | 21 tâches | 24h | ✅ Terminé | |
| 241 | -| **Total** | **109 tâches** | **169 heures** (~21 jours) | ⏳ En cours | | 241 | +| **Total** | **109 tâches** | **169 heures** (~21 jours) | ✅ Terminé | |
| 242 | 242 | ||
| 243 | --- | 243 | --- |
| 244 | 244 | ||
modified
spec/progress.md +1 -1 | @@ -1,6 +1,6 @@ | ||
| 1 | 1 | # RSS Podcast Downloader - Suivi des Progrès |
| 2 | 2 | |
| 3 | -*Dernière mise à jour: 08/07/2026* | |
| 3 | +*Dernière mise à jour: 08/07/2026* (Corrections de compilation et mise à jour des fichiers de suivi) | |
| 4 | 4 | *Version: 4.0* |
| 5 | 5 | |
| 6 | 6 | --- |
| @@ -1,6 +1,6 @@ | |||
| 1 | # RSS Podcast Downloader - Suivi des Progrès | 1 | # RSS Podcast Downloader - Suivi des Progrès |
| 2 | 2 | ||
| 3 | -*Dernière mise à jour: 08/07/2026* | 3 | +*Dernière mise à jour: 08/07/2026* (Corrections de compilation et mise à jour des fichiers de suivi) |
| 4 | *Version: 4.0* | 4 | *Version: 4.0* |
| 5 | 5 | ||
| 6 | --- | 6 | --- |
modified
src/app.rs +56 -3 | @@ -106,9 +106,14 @@ impl eframe::App for RssPodcastApp { | ||
| 106 | 106 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { |
| 107 | 107 | ctx.send_viewport_cmd(egui::ViewportCommand::Title(self.title.clone())); |
| 108 | 108 | |
| 109 | + // Appliquer un thème personnalisé moderne | |
| 110 | + self.apply_custom_theme(ctx); | |
| 111 | + | |
| 109 | 112 | // Draw notifications panel at the top |
| 110 | 113 | egui::TopBottomPanel::top("notifications_panel") |
| 111 | - .frame(egui::Frame::default().fill(egui::Color32::TRANSPARENT)) | |
| 114 | + .frame(egui::Frame::default() | |
| 115 | + .fill(egui::Color32::from_rgb(40, 42, 54)) | |
| 116 | + .rounding(egui::Rounding::same(6.0))) | |
| 112 | 117 | .show(ctx, |ui| { |
| 113 | 118 | ui.vertical_centered(|ui| { |
| 114 | 119 | self.state.notification_manager.draw(ui); |
| @@ -116,9 +121,12 @@ impl eframe::App for RssPodcastApp { | ||
| 116 | 121 | }); |
| 117 | 122 | |
| 118 | 123 | egui::CentralPanel::default() |
| 119 | - .frame(egui::Frame::default().fill(self.clear_color)) | |
| 124 | + .frame(egui::Frame::default() | |
| 125 | + .fill(self.clear_color) | |
| 126 | + .inner_margin(egui::Margin::symmetric(12.0, 8.0))) | |
| 120 | 127 | .show(ctx, |ui| { |
| 121 | - ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 8.0); | |
| 128 | + ui.spacing_mut().item_spacing = egui::Vec2::new(12.0, 12.0); | |
| 129 | + ui.spacing_mut().window_margin = egui::Margin::symmetric(16.0, 16.0); | |
| 122 | 130 | crate::ui::home::draw_home(&mut self.state, ui); |
| 123 | 131 | ui.separator(); |
| 124 | 132 | crate::ui::status_bar::draw_status_bar(&mut self.state, ui); |
| @@ -126,6 +134,51 @@ impl eframe::App for RssPodcastApp { | ||
| 126 | 134 | } |
| 127 | 135 | } |
| 128 | 136 | |
| 137 | +impl RssPodcastApp { | |
| 138 | + /// Applique un thème personnalisé moderne et agréable | |
| 139 | + fn apply_custom_theme(&self, ctx: &egui::Context) { | |
| 140 | + let mut style = (*ctx.style()).clone(); | |
| 141 | + | |
| 142 | + // Couleurs de base (thème sombre moderne inspiré de Nord/ Dracula) | |
| 143 | + style.visuals.dark_mode = true; | |
| 144 | + | |
| 145 | + // Couleurs principales | |
| 146 | + let bg_primary = egui::Color32::from_rgb(36, 39, 58); | |
| 147 | + let bg_secondary = egui::Color32::from_rgb(46, 52, 72); | |
| 148 | + let bg_tertiary = egui::Color32::from_rgb(56, 62, 80); | |
| 149 | + let accent = egui::Color32::from_rgb(130, 170, 250); | |
| 150 | + let border = egui::Color32::from_rgb(80, 88, 106); | |
| 151 | + | |
| 152 | + // Arrière-plan | |
| 153 | + style.visuals.widgets.noninteractive.bg_fill = bg_primary; | |
| 154 | + style.visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, border); | |
| 155 | + | |
| 156 | + // Boutons normaux | |
| 157 | + style.visuals.widgets.inactive.bg_fill = bg_secondary; | |
| 158 | + style.visuals.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, border); | |
| 159 | + | |
| 160 | + // Boutons hover | |
| 161 | + style.visuals.widgets.hovered.bg_fill = bg_tertiary; | |
| 162 | + | |
| 163 | + // Boutons actifs | |
| 164 | + style.visuals.widgets.active.bg_fill = accent; | |
| 165 | + style.visuals.widgets.active.bg_stroke = egui::Stroke::new(1.0, accent); | |
| 166 | + | |
| 167 | + // Espacement amélioré | |
| 168 | + style.spacing.item_spacing = egui::Vec2::new(12.0, 12.0); | |
| 169 | + style.spacing.button_padding = egui::Vec2::new(12.0, 6.0); | |
| 170 | + | |
| 171 | + // Taille des icônes | |
| 172 | + style.spacing.icon_width = 20.0; | |
| 173 | + style.spacing.icon_spacing = 8.0; | |
| 174 | + | |
| 175 | + // Couleur des liens | |
| 176 | + style.visuals.hyperlink_color = accent; | |
| 177 | + | |
| 178 | + ctx.set_style(style); | |
| 179 | + } | |
| 180 | +} | |
| 181 | + | |
| 129 | 182 | pub fn run() -> Result<(), Box<dyn std::error::Error>> { |
| 130 | 183 | info!("Initializing egui application"); |
| 131 | 184 | |
| @@ -106,9 +106,14 @@ impl eframe::App for RssPodcastApp { | |||
| 106 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { | 106 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { |
| 107 | ctx.send_viewport_cmd(egui::ViewportCommand::Title(self.title.clone())); | 107 | ctx.send_viewport_cmd(egui::ViewportCommand::Title(self.title.clone())); |
| 108 | 108 | ||
| 109 | + // Appliquer un thème personnalisé moderne | ||
| 110 | + self.apply_custom_theme(ctx); | ||
| 111 | + | ||
| 109 | // Draw notifications panel at the top | 112 | // Draw notifications panel at the top |
| 110 | egui::TopBottomPanel::top("notifications_panel") | 113 | egui::TopBottomPanel::top("notifications_panel") |
| 111 | - .frame(egui::Frame::default().fill(egui::Color32::TRANSPARENT)) | 114 | + .frame(egui::Frame::default() |
| 115 | + .fill(egui::Color32::from_rgb(40, 42, 54)) | ||
| 116 | + .rounding(egui::Rounding::same(6.0))) | ||
| 112 | .show(ctx, |ui| { | 117 | .show(ctx, |ui| { |
| 113 | ui.vertical_centered(|ui| { | 118 | ui.vertical_centered(|ui| { |
| 114 | self.state.notification_manager.draw(ui); | 119 | self.state.notification_manager.draw(ui); |
| @@ -116,9 +121,12 @@ impl eframe::App for RssPodcastApp { | |||
| 116 | }); | 121 | }); |
| 117 | 122 | ||
| 118 | egui::CentralPanel::default() | 123 | egui::CentralPanel::default() |
| 119 | - .frame(egui::Frame::default().fill(self.clear_color)) | 124 | + .frame(egui::Frame::default() |
| 125 | + .fill(self.clear_color) | ||
| 126 | + .inner_margin(egui::Margin::symmetric(12.0, 8.0))) | ||
| 120 | .show(ctx, |ui| { | 127 | .show(ctx, |ui| { |
| 121 | - ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 8.0); | 128 | + ui.spacing_mut().item_spacing = egui::Vec2::new(12.0, 12.0); |
| 129 | + ui.spacing_mut().window_margin = egui::Margin::symmetric(16.0, 16.0); | ||
| 122 | crate::ui::home::draw_home(&mut self.state, ui); | 130 | crate::ui::home::draw_home(&mut self.state, ui); |
| 123 | ui.separator(); | 131 | ui.separator(); |
| 124 | crate::ui::status_bar::draw_status_bar(&mut self.state, ui); | 132 | crate::ui::status_bar::draw_status_bar(&mut self.state, ui); |
| @@ -126,6 +134,51 @@ impl eframe::App for RssPodcastApp { | |||
| 126 | } | 134 | } |
| 127 | } | 135 | } |
| 128 | 136 | ||
| 137 | +impl RssPodcastApp { | ||
| 138 | + /// Applique un thème personnalisé moderne et agréable | ||
| 139 | + fn apply_custom_theme(&self, ctx: &egui::Context) { | ||
| 140 | + let mut style = (*ctx.style()).clone(); | ||
| 141 | + | ||
| 142 | + // Couleurs de base (thème sombre moderne inspiré de Nord/ Dracula) | ||
| 143 | + style.visuals.dark_mode = true; | ||
| 144 | + | ||
| 145 | + // Couleurs principales | ||
| 146 | + let bg_primary = egui::Color32::from_rgb(36, 39, 58); | ||
| 147 | + let bg_secondary = egui::Color32::from_rgb(46, 52, 72); | ||
| 148 | + let bg_tertiary = egui::Color32::from_rgb(56, 62, 80); | ||
| 149 | + let accent = egui::Color32::from_rgb(130, 170, 250); | ||
| 150 | + let border = egui::Color32::from_rgb(80, 88, 106); | ||
| 151 | + | ||
| 152 | + // Arrière-plan | ||
| 153 | + style.visuals.widgets.noninteractive.bg_fill = bg_primary; | ||
| 154 | + style.visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, border); | ||
| 155 | + | ||
| 156 | + // Boutons normaux | ||
| 157 | + style.visuals.widgets.inactive.bg_fill = bg_secondary; | ||
| 158 | + style.visuals.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, border); | ||
| 159 | + | ||
| 160 | + // Boutons hover | ||
| 161 | + style.visuals.widgets.hovered.bg_fill = bg_tertiary; | ||
| 162 | + | ||
| 163 | + // Boutons actifs | ||
| 164 | + style.visuals.widgets.active.bg_fill = accent; | ||
| 165 | + style.visuals.widgets.active.bg_stroke = egui::Stroke::new(1.0, accent); | ||
| 166 | + | ||
| 167 | + // Espacement amélioré | ||
| 168 | + style.spacing.item_spacing = egui::Vec2::new(12.0, 12.0); | ||
| 169 | + style.spacing.button_padding = egui::Vec2::new(12.0, 6.0); | ||
| 170 | + | ||
| 171 | + // Taille des icônes | ||
| 172 | + style.spacing.icon_width = 20.0; | ||
| 173 | + style.spacing.icon_spacing = 8.0; | ||
| 174 | + | ||
| 175 | + // Couleur des liens | ||
| 176 | + style.visuals.hyperlink_color = accent; | ||
| 177 | + | ||
| 178 | + ctx.set_style(style); | ||
| 179 | + } | ||
| 180 | +} | ||
| 181 | + | ||
| 129 | pub fn run() -> Result<(), Box<dyn std::error::Error>> { | 182 | pub fn run() -> Result<(), Box<dyn std::error::Error>> { |
| 130 | info!("Initializing egui application"); | 183 | info!("Initializing egui application"); |
| 131 | 184 | ||
modified
src/ui/feed_list.rs +114 -65 | @@ -22,81 +22,130 @@ pub fn draw_feed_list(state: &mut AppState, ui: &mut Ui) { | ||
| 22 | 22 | }); |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | -/// Draws a single feed item | |
| 25 | +/// Draws a single feed item with improved UI | |
| 26 | 26 | fn draw_feed_item(state: &mut AppState, ui: &mut Ui, index: usize) { |
| 27 | - let feed_name = state.feeds[index].name.clone(); | |
| 28 | - let feed_url = state.feeds[index].url.clone(); | |
| 29 | - let feed_path = state.feeds[index].download_path.to_string_lossy().to_string(); | |
| 30 | - let feed_enabled = state.feeds[index].enabled; | |
| 31 | - let feed_last_checked = state.feeds[index].last_checked; | |
| 27 | + let feed = &state.feeds[index]; | |
| 28 | + let feed_name = feed.name.clone(); | |
| 29 | + let feed_url = feed.url.clone(); | |
| 30 | + let feed_path = feed.download_path.to_string_lossy().to_string(); | |
| 31 | + let feed_enabled = feed.enabled; | |
| 32 | + let feed_last_checked = feed.last_checked; | |
| 33 | + let feed_tags = feed.tags.clone(); | |
| 32 | 34 | let is_selected = state.current_feed == Some(index); |
| 33 | 35 | |
| 34 | - let frame_color = if is_selected { | |
| 35 | - egui::Color32::LIGHT_BLUE | |
| 36 | + let bg_color = if is_selected { | |
| 37 | + egui::Color32::from_rgb(65, 72, 88) | |
| 36 | 38 | } else { |
| 37 | - egui::Color32::from_gray(60) | |
| 39 | + egui::Color32::from_rgb(46, 52, 72) | |
| 38 | 40 | }; |
| 39 | 41 | |
| 40 | - let frame = egui::Frame::default().fill(frame_color); | |
| 41 | - | |
| 42 | - frame.show(ui, |ui| { | |
| 43 | - ui.horizontal(|ui| { | |
| 44 | - // Selection | |
| 45 | - if ui.selectable_label(is_selected, &feed_name).clicked() { | |
| 46 | - state.select_feed(Some(index)); | |
| 47 | - } | |
| 48 | - | |
| 49 | - ui.label(format!("URL: {}", feed_url)); | |
| 50 | - ui.label(format!("Dossier: {}", feed_path)); | |
| 51 | - | |
| 52 | - // Enabled toggle | |
| 53 | - let mut enabled = feed_enabled; | |
| 54 | - if ui.checkbox(&mut enabled, "Activé").changed() { | |
| 55 | - state.feeds[index].enabled = enabled; | |
| 56 | - super::super::config::storage::save_feeds_default(&state.feeds).ok(); | |
| 57 | - } | |
| 58 | - | |
| 59 | - // Tags | |
| 60 | - let feed = &state.feeds[index]; | |
| 61 | - if !feed.tags.is_empty() { | |
| 62 | - ui.label(format!("Tags: {}", feed.tags_string())); | |
| 63 | - } | |
| 64 | - | |
| 65 | - // Last checked | |
| 66 | - if let Some(date) = feed_last_checked { | |
| 67 | - ui.label(format!("Vérifié: {}", date.format("%Y-%m-%d %H:%M"))); | |
| 68 | - } else { | |
| 69 | - ui.label("Jamais vérifié"); | |
| 70 | - } | |
| 71 | - | |
| 72 | - // Actions | |
| 73 | - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | |
| 74 | - // Edit button | |
| 75 | - if ui.button("✏️").on_hover_text("Éditer").clicked() { | |
| 76 | - state.editing_feed_index = Some(index); | |
| 77 | - state.load_edit_feed_form(index); | |
| 78 | - state.show_edit_feed_dialog = true; | |
| 79 | - } | |
| 80 | - | |
| 81 | - // Sync button | |
| 82 | - if ui.button("🔄").on_hover_text("Synchroniser").clicked() { | |
| 83 | - sync_feed(state, index); | |
| 84 | - } | |
| 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 | + }; | |
| 85 | 47 | |
| 86 | - // Delete button | |
| 87 | - if ui.button("🗑️").on_hover_text("Supprimer").clicked() { | |
| 88 | - log::info!("Deleting feed at index {}: {}", index, state.feeds[index].name); | |
| 89 | - state.feeds.remove(index); | |
| 90 | - super::super::config::storage::save_feeds_default(&state.feeds).ok(); | |
| 91 | - log::info!("Feed deleted successfully"); | |
| 92 | - state.notification_manager.success("Flux supprimé"); | |
| 93 | - if state.current_feed == Some(index) { | |
| 94 | - state.select_feed(None); | |
| 48 | + // Frame avec style amélioré | |
| 49 | + egui::Frame::default() | |
| 50 | + .fill(bg_color) | |
| 51 | + .stroke(egui::Stroke::new(1.0, stroke_color)) | |
| 52 | + .rounding(egui::Rounding::same(6.0)) | |
| 53 | + .inner_margin(egui::Margin::symmetric(12.0, 8.0)) | |
| 54 | + .show(ui, |ui| { | |
| 55 | + ui.spacing_mut().item_spacing = egui::Vec2::new(16.0, 8.0); | |
| 56 | + | |
| 57 | + ui.horizontal(|ui| { | |
| 58 | + // Section info principale | |
| 59 | + ui.vertical(|ui| { | |
| 60 | + // Nom du flux (plus gros et en gras) | |
| 61 | + ui.label(egui::RichText::new(&feed_name) | |
| 62 | + .size(16.0) | |
| 63 | + .strong() | |
| 64 | + .color(egui::Color32::from_rgb(220, 230, 245))); | |
| 65 | + | |
| 66 | + // URL (plus petite) | |
| 67 | + ui.label(egui::RichText::new(format!("🔗 {}", feed_url)) | |
| 68 | + .size(12.0) | |
| 69 | + .color(egui::Color32::from_rgb(150, 170, 200))); | |
| 70 | + | |
| 71 | + // Dossier | |
| 72 | + ui.label(egui::RichText::new(format!("📁 {}", feed_path)) | |
| 73 | + .size(12.0) | |
| 74 | + .color(egui::Color32::from_rgb(150, 170, 200))); | |
| 75 | + | |
| 76 | + // Tags | |
| 77 | + if !feed_tags.is_empty() { | |
| 78 | + ui.horizontal_wrapped(|ui| { | |
| 79 | + ui.label(egui::RichText::new("🏷️ ").size(12.0)); | |
| 80 | + for tag in &feed_tags { | |
| 81 | + ui.label(egui::RichText::new(format!("#{} ", tag)) | |
| 82 | + .size(11.0) | |
| 83 | + .color(egui::Color32::from_rgb(180, 190, 220))); | |
| 84 | + } | |
| 85 | + }); | |
| 86 | + } | |
| 87 | + }); | |
| 88 | + | |
| 89 | + // Section statut | |
| 90 | + ui.vertical(|ui| { | |
| 91 | + ui.add_space(4.0); | |
| 92 | + | |
| 93 | + // Statut Activé/Désactivé | |
| 94 | + let status_text = if feed_enabled { | |
| 95 | + egui::RichText::new("✅ Activé").color(egui::Color32::from_rgb(80, 220, 100)) | |
| 96 | + } else { | |
| 97 | + egui::RichText::new("❌ Désactivé").color(egui::Color32::from_rgb(220, 80, 80)) | |
| 98 | + }; | |
| 99 | + ui.label(status_text.size(12.0)); | |
| 100 | + | |
| 101 | + // Dernière vérification | |
| 102 | + if let Some(date) = feed_last_checked { | |
| 103 | + ui.label(egui::RichText::new(format!("🔍 Vérifié: {}", date.format("%d/%m %H:%M"))) | |
| 104 | + .size(11.0) | |
| 105 | + .color(egui::Color32::from_rgb(160, 180, 200))); | |
| 106 | + } else { | |
| 107 | + ui.label(egui::RichText::new("🔍 Jamais vérifié").size(11.0).color(egui::Color32::from_rgb(160, 180, 200))); | |
| 108 | + } | |
| 109 | + }); | |
| 110 | + | |
| 111 | + // Section actions (à droite) | |
| 112 | + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | |
| 113 | + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); | |
| 114 | + | |
| 115 | + // Bouton Synchroniser | |
| 116 | + if ui.button(egui::RichText::new("🔄").size(18.0)) | |
| 117 | + .on_hover_text("Synchroniser ce flux") | |
| 118 | + .clicked() { | |
| 119 | + sync_feed(state, index); | |
| 120 | + } | |
| 121 | + | |
| 122 | + // Bouton Éditer | |
| 123 | + if ui.button(egui::RichText::new("✏️").size(18.0)) | |
| 124 | + .on_hover_text("Éditer ce flux") | |
| 125 | + .clicked() { | |
| 126 | + state.editing_feed_index = Some(index); | |
| 127 | + state.load_edit_feed_form(index); | |
| 128 | + state.show_edit_feed_dialog = true; | |
| 95 | 129 | } |
| 96 | - } | |
| 130 | + | |
| 131 | + // Bouton Supprimer | |
| 132 | + if ui.button(egui::RichText::new("🗑️").size(18.0)) | |
| 133 | + .on_hover_text("Supprimer ce flux") | |
| 134 | + .clicked() { | |
| 135 | + log::info!("Deleting feed at index {}: {}", index, feed_name); | |
| 136 | + state.feeds.remove(index); | |
| 137 | + super::super::config::storage::save_feeds_default(&state.feeds).ok(); | |
| 138 | + log::info!("Feed deleted successfully"); | |
| 139 | + state.notification_manager.success("Flux supprimé"); | |
| 140 | + if state.current_feed == Some(index) { | |
| 141 | + state.select_feed(None); | |
| 142 | + } | |
| 143 | + } | |
| 144 | + }); | |
| 97 | 145 | }); |
| 98 | 146 | }); |
| 99 | - }); | |
| 147 | + | |
| 148 | + ui.add_space(6.0); | |
| 100 | 149 | |
| 101 | 150 | ui.separator(); |
| 102 | 151 | } |
| @@ -22,81 +22,130 @@ pub fn draw_feed_list(state: &mut AppState, ui: &mut Ui) { | |||
| 22 | }); | 22 | }); |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | -/// Draws a single feed item | 25 | +/// Draws a single feed item with improved UI |
| 26 | fn draw_feed_item(state: &mut AppState, ui: &mut Ui, index: usize) { | 26 | fn draw_feed_item(state: &mut AppState, ui: &mut Ui, index: usize) { |
| 27 | - let feed_name = state.feeds[index].name.clone(); | 27 | + let feed = &state.feeds[index]; |
| 28 | - let feed_url = state.feeds[index].url.clone(); | 28 | + let feed_name = feed.name.clone(); |
| 29 | - let feed_path = state.feeds[index].download_path.to_string_lossy().to_string(); | 29 | + let feed_url = feed.url.clone(); |
| 30 | - let feed_enabled = state.feeds[index].enabled; | 30 | + let feed_path = feed.download_path.to_string_lossy().to_string(); |
| 31 | - let feed_last_checked = state.feeds[index].last_checked; | 31 | + let feed_enabled = feed.enabled; |
| 32 | + let feed_last_checked = feed.last_checked; | ||
| 33 | + let feed_tags = feed.tags.clone(); | ||
| 32 | let is_selected = state.current_feed == Some(index); | 34 | let is_selected = state.current_feed == Some(index); |
| 33 | 35 | ||
| 34 | - let frame_color = if is_selected { | 36 | + let bg_color = if is_selected { |
| 35 | - egui::Color32::LIGHT_BLUE | 37 | + egui::Color32::from_rgb(65, 72, 88) |
| 36 | } else { | 38 | } else { |
| 37 | - egui::Color32::from_gray(60) | 39 | + egui::Color32::from_rgb(46, 52, 72) |
| 38 | }; | 40 | }; |
| 39 | 41 | ||
| 40 | - let frame = egui::Frame::default().fill(frame_color); | 42 | + let stroke_color = if is_selected { |
| 41 | - | 43 | + egui::Color32::from_rgb(130, 170, 250) |
| 42 | - frame.show(ui, |ui| { | 44 | + } else { |
| 43 | - ui.horizontal(|ui| { | 45 | + egui::Color32::from_rgb(70, 78, 95) |
| 44 | - // Selection | 46 | + }; |
| 45 | - if ui.selectable_label(is_selected, &feed_name).clicked() { | ||
| 46 | - state.select_feed(Some(index)); | ||
| 47 | - } | ||
| 48 | - | ||
| 49 | - ui.label(format!("URL: {}", feed_url)); | ||
| 50 | - ui.label(format!("Dossier: {}", feed_path)); | ||
| 51 | - | ||
| 52 | - // Enabled toggle | ||
| 53 | - let mut enabled = feed_enabled; | ||
| 54 | - if ui.checkbox(&mut enabled, "Activé").changed() { | ||
| 55 | - state.feeds[index].enabled = enabled; | ||
| 56 | - super::super::config::storage::save_feeds_default(&state.feeds).ok(); | ||
| 57 | - } | ||
| 58 | - | ||
| 59 | - // Tags | ||
| 60 | - let feed = &state.feeds[index]; | ||
| 61 | - if !feed.tags.is_empty() { | ||
| 62 | - ui.label(format!("Tags: {}", feed.tags_string())); | ||
| 63 | - } | ||
| 64 | - | ||
| 65 | - // Last checked | ||
| 66 | - if let Some(date) = feed_last_checked { | ||
| 67 | - ui.label(format!("Vérifié: {}", date.format("%Y-%m-%d %H:%M"))); | ||
| 68 | - } else { | ||
| 69 | - ui.label("Jamais vérifié"); | ||
| 70 | - } | ||
| 71 | - | ||
| 72 | - // Actions | ||
| 73 | - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | ||
| 74 | - // Edit button | ||
| 75 | - if ui.button("✏️").on_hover_text("Éditer").clicked() { | ||
| 76 | - state.editing_feed_index = Some(index); | ||
| 77 | - state.load_edit_feed_form(index); | ||
| 78 | - state.show_edit_feed_dialog = true; | ||
| 79 | - } | ||
| 80 | - | ||
| 81 | - // Sync button | ||
| 82 | - if ui.button("🔄").on_hover_text("Synchroniser").clicked() { | ||
| 83 | - sync_feed(state, index); | ||
| 84 | - } | ||
| 85 | 47 | ||
| 86 | - // Delete button | 48 | + // Frame avec style amélioré |
| 87 | - if ui.button("🗑️").on_hover_text("Supprimer").clicked() { | 49 | + egui::Frame::default() |
| 88 | - log::info!("Deleting feed at index {}: {}", index, state.feeds[index].name); | 50 | + .fill(bg_color) |
| 89 | - state.feeds.remove(index); | 51 | + .stroke(egui::Stroke::new(1.0, stroke_color)) |
| 90 | - super::super::config::storage::save_feeds_default(&state.feeds).ok(); | 52 | + .rounding(egui::Rounding::same(6.0)) |
| 91 | - log::info!("Feed deleted successfully"); | 53 | + .inner_margin(egui::Margin::symmetric(12.0, 8.0)) |
| 92 | - state.notification_manager.success("Flux supprimé"); | 54 | + .show(ui, |ui| { |
| 93 | - if state.current_feed == Some(index) { | 55 | + ui.spacing_mut().item_spacing = egui::Vec2::new(16.0, 8.0); |
| 94 | - state.select_feed(None); | 56 | + |
| 57 | + ui.horizontal(|ui| { | ||
| 58 | + // Section info principale | ||
| 59 | + ui.vertical(|ui| { | ||
| 60 | + // Nom du flux (plus gros et en gras) | ||
| 61 | + ui.label(egui::RichText::new(&feed_name) | ||
| 62 | + .size(16.0) | ||
| 63 | + .strong() | ||
| 64 | + .color(egui::Color32::from_rgb(220, 230, 245))); | ||
| 65 | + | ||
| 66 | + // URL (plus petite) | ||
| 67 | + ui.label(egui::RichText::new(format!("🔗 {}", feed_url)) | ||
| 68 | + .size(12.0) | ||
| 69 | + .color(egui::Color32::from_rgb(150, 170, 200))); | ||
| 70 | + | ||
| 71 | + // Dossier | ||
| 72 | + ui.label(egui::RichText::new(format!("📁 {}", feed_path)) | ||
| 73 | + .size(12.0) | ||
| 74 | + .color(egui::Color32::from_rgb(150, 170, 200))); | ||
| 75 | + | ||
| 76 | + // Tags | ||
| 77 | + if !feed_tags.is_empty() { | ||
| 78 | + ui.horizontal_wrapped(|ui| { | ||
| 79 | + ui.label(egui::RichText::new("🏷️ ").size(12.0)); | ||
| 80 | + for tag in &feed_tags { | ||
| 81 | + ui.label(egui::RichText::new(format!("#{} ", tag)) | ||
| 82 | + .size(11.0) | ||
| 83 | + .color(egui::Color32::from_rgb(180, 190, 220))); | ||
| 84 | + } | ||
| 85 | + }); | ||
| 86 | + } | ||
| 87 | + }); | ||
| 88 | + | ||
| 89 | + // Section statut | ||
| 90 | + ui.vertical(|ui| { | ||
| 91 | + ui.add_space(4.0); | ||
| 92 | + | ||
| 93 | + // Statut Activé/Désactivé | ||
| 94 | + let status_text = if feed_enabled { | ||
| 95 | + egui::RichText::new("✅ Activé").color(egui::Color32::from_rgb(80, 220, 100)) | ||
| 96 | + } else { | ||
| 97 | + egui::RichText::new("❌ Désactivé").color(egui::Color32::from_rgb(220, 80, 80)) | ||
| 98 | + }; | ||
| 99 | + ui.label(status_text.size(12.0)); | ||
| 100 | + | ||
| 101 | + // Dernière vérification | ||
| 102 | + if let Some(date) = feed_last_checked { | ||
| 103 | + ui.label(egui::RichText::new(format!("🔍 Vérifié: {}", date.format("%d/%m %H:%M"))) | ||
| 104 | + .size(11.0) | ||
| 105 | + .color(egui::Color32::from_rgb(160, 180, 200))); | ||
| 106 | + } else { | ||
| 107 | + ui.label(egui::RichText::new("🔍 Jamais vérifié").size(11.0).color(egui::Color32::from_rgb(160, 180, 200))); | ||
| 108 | + } | ||
| 109 | + }); | ||
| 110 | + | ||
| 111 | + // Section actions (à droite) | ||
| 112 | + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | ||
| 113 | + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); | ||
| 114 | + | ||
| 115 | + // Bouton Synchroniser | ||
| 116 | + if ui.button(egui::RichText::new("🔄").size(18.0)) | ||
| 117 | + .on_hover_text("Synchroniser ce flux") | ||
| 118 | + .clicked() { | ||
| 119 | + sync_feed(state, index); | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + // Bouton Éditer | ||
| 123 | + if ui.button(egui::RichText::new("✏️").size(18.0)) | ||
| 124 | + .on_hover_text("Éditer ce flux") | ||
| 125 | + .clicked() { | ||
| 126 | + state.editing_feed_index = Some(index); | ||
| 127 | + state.load_edit_feed_form(index); | ||
| 128 | + state.show_edit_feed_dialog = true; | ||
| 95 | } | 129 | } |
| 96 | - } | 130 | + |
| 131 | + // Bouton Supprimer | ||
| 132 | + if ui.button(egui::RichText::new("🗑️").size(18.0)) | ||
| 133 | + .on_hover_text("Supprimer ce flux") | ||
| 134 | + .clicked() { | ||
| 135 | + log::info!("Deleting feed at index {}: {}", index, feed_name); | ||
| 136 | + state.feeds.remove(index); | ||
| 137 | + super::super::config::storage::save_feeds_default(&state.feeds).ok(); | ||
| 138 | + log::info!("Feed deleted successfully"); | ||
| 139 | + state.notification_manager.success("Flux supprimé"); | ||
| 140 | + if state.current_feed == Some(index) { | ||
| 141 | + state.select_feed(None); | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | + }); | ||
| 97 | }); | 145 | }); |
| 98 | }); | 146 | }); |
| 99 | - }); | 147 | + |
| 148 | + ui.add_space(6.0); | ||
| 100 | 149 | ||
| 101 | ui.separator(); | 150 | ui.separator(); |
| 102 | } | 151 | } |
modified
src/ui/home.rs +50 -33 | @@ -3,76 +3,86 @@ | ||
| 3 | 3 | //! This module contains the main home screen layout. |
| 4 | 4 | |
| 5 | 5 | use crate::config::AppState; |
| 6 | -use egui::{RichText, Ui}; | |
| 6 | +use egui::Ui; | |
| 7 | 7 | |
| 8 | 8 | /// Draws the home screen |
| 9 | 9 | pub fn draw_home(state: &mut AppState, ui: &mut Ui) { |
| 10 | - // Top bar with title | |
| 10 | + // Header avec titre et icône | |
| 11 | 11 | ui.horizontal(|ui| { |
| 12 | - ui.heading("RSS Podcast Downloader"); | |
| 12 | + ui.heading(egui::RichText::new("🎙️ RSS Podcast Downloader").color(egui::Color32::from_rgb(130, 170, 250))); | |
| 13 | + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { | |
| 14 | + ui.label(egui::RichText::new(format!("v{}", env!("CARGO_PKG_VERSION"))).color(egui::Color32::from_rgb(150, 190, 255))); | |
| 15 | + }); | |
| 13 | 16 | }); |
| 14 | 17 | |
| 15 | 18 | ui.separator(); |
| 16 | 19 | |
| 17 | - // Filter panel | |
| 18 | - crate::ui::filter_panel::draw_filter_panel(state, ui); | |
| 20 | + // Barre d'outils avec filtres et actions | |
| 21 | + ui.horizontal(|ui| { | |
| 22 | + ui.vertical(|ui| { | |
| 23 | + // Filtres | |
| 24 | + crate::ui::filter_panel::draw_filter_panel(state, ui); | |
| 25 | + }); | |
| 26 | + }); | |
| 27 | + | |
| 28 | + ui.separator(); | |
| 19 | 29 | |
| 20 | - // Action buttons | |
| 21 | - draw_action_buttons(state, ui); | |
| 30 | + // Boutons d'action principaux (améliorés) | |
| 31 | + ui.horizontal(|ui| { | |
| 32 | + ui.spacing_mut().item_spacing = egui::Vec2::new(16.0, 0.0); | |
| 33 | + draw_action_buttons(state, ui); | |
| 34 | + }); | |
| 22 | 35 | |
| 36 | + ui.add_space(16.0); | |
| 23 | 37 | ui.separator(); |
| 24 | 38 | |
| 25 | - // Feed list or empty message | |
| 26 | - if state.has_feeds() { | |
| 27 | - draw_feed_list(state, ui); | |
| 28 | - } else { | |
| 29 | - draw_empty_state(ui); | |
| 30 | - } | |
| 39 | + // Section des flux | |
| 40 | + ui.collapsing("📻 Mes Flux RSS", |ui| { | |
| 41 | + if state.has_feeds() { | |
| 42 | + draw_feed_list(state, ui); | |
| 43 | + } else { | |
| 44 | + draw_empty_state(ui); | |
| 45 | + } | |
| 46 | + }); | |
| 31 | 47 | |
| 32 | 48 | ui.separator(); |
| 33 | 49 | |
| 34 | - // Playlist list | |
| 35 | - crate::ui::playlist_list::draw_playlist_list(state, ui); | |
| 50 | + // Section des playlists | |
| 51 | + ui.collapsing("📋 Playlists", |ui| { | |
| 52 | + crate::ui::playlist_list::draw_playlist_list(state, ui); | |
| 53 | + }); | |
| 36 | 54 | |
| 37 | - // Draw add feed dialog | |
| 55 | + // Dialogues | |
| 38 | 56 | crate::ui::add_feed_dialog::draw_add_feed_dialog(state, ui); |
| 39 | - | |
| 40 | - // Draw edit feed dialog | |
| 41 | 57 | crate::ui::feed_edit_dialog::draw_edit_feed_dialog(state, ui); |
| 42 | - | |
| 43 | - // Draw add playlist dialog | |
| 44 | 58 | crate::ui::playlist_list::draw_add_playlist_dialog(state, ui); |
| 45 | - | |
| 46 | - // Draw edit playlist dialog | |
| 47 | 59 | crate::ui::playlist_list::draw_edit_playlist_dialog(state, ui); |
| 48 | - | |
| 49 | - // Draw export dialog | |
| 50 | 60 | crate::ui::export_import::draw_export_dialog(state, ui); |
| 51 | - | |
| 52 | - // Draw import dialog | |
| 53 | 61 | crate::ui::export_import::draw_import_dialog(state, ui); |
| 54 | 62 | } |
| 55 | 63 | |
| 56 | 64 | /// Draws the action buttons |
| 57 | 65 | fn draw_action_buttons(state: &mut AppState, ui: &mut Ui) { |
| 58 | 66 | ui.horizontal(|ui| { |
| 59 | - if ui.button("📥 Ajouter un flux").clicked() { | |
| 67 | + // Boutons principaux avec icônes et style amélioré | |
| 68 | + if ui.button(egui::RichText::new("➕ Ajouter un flux").size(14.0)).clicked() { | |
| 60 | 69 | state.clear_new_feed_form(); |
| 61 | 70 | state.show_add_feed_dialog = true; |
| 62 | 71 | } |
| 63 | 72 | |
| 64 | - if ui.button("🔄 Synchroniser tous").clicked() { | |
| 73 | + if ui.button(egui::RichText::new("🔄 Synchroniser tous").size(14.0)).clicked() { | |
| 65 | 74 | sync_all_feeds(state); |
| 66 | 75 | } |
| 67 | 76 | |
| 68 | 77 | ui.add_space(10.0); |
| 69 | 78 | |
| 70 | - if ui.button("📤 Exporter").clicked() { | |
| 79 | + // Boutons secondaires | |
| 80 | + if ui.button(egui::RichText::new("📤 Exporter").size(14.0)).clicked() { | |
| 71 | 81 | state.clear_export_import_error(); |
| 72 | 82 | state.show_export_dialog = true; |
| 73 | 83 | } |
| 74 | 84 | |
| 75 | - if ui.button("📥 Importer").clicked() { | |
| 85 | + if ui.button(egui::RichText::new("📥 Importer").size(14.0)).clicked() { | |
| 76 | 86 | state.clear_export_import_error(); |
| 77 | 87 | state.show_import_dialog = true; |
| 78 | 88 | } |
| @@ -81,9 +91,16 @@ fn draw_action_buttons(state: &mut AppState, ui: &mut Ui) { | ||
| 81 | 91 | |
| 82 | 92 | /// Draws the empty state message |
| 83 | 93 | fn draw_empty_state(ui: &mut Ui) { |
| 84 | - ui.centered_and_justified(|ui| { | |
| 85 | - ui.label(RichText::new("Aucun flux configuré").size(18.0)); | |
| 86 | - ui.label("Cliquez sur 'Ajouter un flux' pour commencer"); | |
| 94 | + ui.vertical_centered_justified(|ui| { | |
| 95 | + ui.add_space(40.0); | |
| 96 | + ui.label(egui::RichText::new("🎧").size(48.0)); | |
| 97 | + ui.add_space(16.0); | |
| 98 | + ui.label(egui::RichText::new("Bienvenue dans RSS Podcast Downloader").size(20.0).color(egui::Color32::from_rgb(220, 230, 245))); | |
| 99 | + ui.add_space(8.0); | |
| 100 | + ui.label(egui::RichText::new("Aucun flux configuré pour le moment").size(16.0).color(egui::Color32::from_rgb(180, 190, 210))); | |
| 101 | + ui.add_space(16.0); | |
| 102 | + ui.label(egui::RichText::new("👉 Cliquez sur '➕ Ajouter un flux' pour commencer").size(14.0).color(egui::Color32::from_rgb(150, 190, 255))); | |
| 103 | + ui.add_space(40.0); | |
| 87 | 104 | }); |
| 88 | 105 | } |
| 89 | 106 | |
| @@ -3,76 +3,86 @@ | |||
| 3 | //! This module contains the main home screen layout. | 3 | //! This module contains the main home screen layout. |
| 4 | 4 | ||
| 5 | use crate::config::AppState; | 5 | use crate::config::AppState; |
| 6 | -use egui::{RichText, Ui}; | 6 | +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 | - // Top bar with title | 10 | + // Header avec titre et icône |
| 11 | ui.horizontal(|ui| { | 11 | ui.horizontal(|ui| { |
| 12 | - ui.heading("RSS Podcast Downloader"); | 12 | + ui.heading(egui::RichText::new("🎙️ RSS Podcast Downloader").color(egui::Color32::from_rgb(130, 170, 250))); |
| 13 | + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { | ||
| 14 | + ui.label(egui::RichText::new(format!("v{}", env!("CARGO_PKG_VERSION"))).color(egui::Color32::from_rgb(150, 190, 255))); | ||
| 15 | + }); | ||
| 13 | }); | 16 | }); |
| 14 | 17 | ||
| 15 | ui.separator(); | 18 | ui.separator(); |
| 16 | 19 | ||
| 17 | - // Filter panel | 20 | + // Barre d'outils avec filtres et actions |
| 18 | - crate::ui::filter_panel::draw_filter_panel(state, ui); | 21 | + ui.horizontal(|ui| { |
| 22 | + ui.vertical(|ui| { | ||
| 23 | + // Filtres | ||
| 24 | + crate::ui::filter_panel::draw_filter_panel(state, ui); | ||
| 25 | + }); | ||
| 26 | + }); | ||
| 27 | + | ||
| 28 | + ui.separator(); | ||
| 19 | 29 | ||
| 20 | - // Action buttons | 30 | + // Boutons d'action principaux (améliorés) |
| 21 | - draw_action_buttons(state, ui); | 31 | + ui.horizontal(|ui| { |
| 32 | + ui.spacing_mut().item_spacing = egui::Vec2::new(16.0, 0.0); | ||
| 33 | + draw_action_buttons(state, ui); | ||
| 34 | + }); | ||
| 22 | 35 | ||
| 36 | + ui.add_space(16.0); | ||
| 23 | ui.separator(); | 37 | ui.separator(); |
| 24 | 38 | ||
| 25 | - // Feed list or empty message | 39 | + // Section des flux |
| 26 | - if state.has_feeds() { | 40 | + ui.collapsing("📻 Mes Flux RSS", |ui| { |
| 27 | - draw_feed_list(state, ui); | 41 | + if state.has_feeds() { |
| 28 | - } else { | 42 | + draw_feed_list(state, ui); |
| 29 | - draw_empty_state(ui); | 43 | + } else { |
| 30 | - } | 44 | + draw_empty_state(ui); |
| 45 | + } | ||
| 46 | + }); | ||
| 31 | 47 | ||
| 32 | ui.separator(); | 48 | ui.separator(); |
| 33 | 49 | ||
| 34 | - // Playlist list | 50 | + // Section des playlists |
| 35 | - crate::ui::playlist_list::draw_playlist_list(state, ui); | 51 | + ui.collapsing("📋 Playlists", |ui| { |
| 52 | + crate::ui::playlist_list::draw_playlist_list(state, ui); | ||
| 53 | + }); | ||
| 36 | 54 | ||
| 37 | - // Draw add feed dialog | 55 | + // Dialogues |
| 38 | crate::ui::add_feed_dialog::draw_add_feed_dialog(state, ui); | 56 | crate::ui::add_feed_dialog::draw_add_feed_dialog(state, ui); |
| 39 | - | ||
| 40 | - // Draw edit feed dialog | ||
| 41 | crate::ui::feed_edit_dialog::draw_edit_feed_dialog(state, ui); | 57 | crate::ui::feed_edit_dialog::draw_edit_feed_dialog(state, ui); |
| 42 | - | ||
| 43 | - // Draw add playlist dialog | ||
| 44 | crate::ui::playlist_list::draw_add_playlist_dialog(state, ui); | 58 | crate::ui::playlist_list::draw_add_playlist_dialog(state, ui); |
| 45 | - | ||
| 46 | - // Draw edit playlist dialog | ||
| 47 | crate::ui::playlist_list::draw_edit_playlist_dialog(state, ui); | 59 | crate::ui::playlist_list::draw_edit_playlist_dialog(state, ui); |
| 48 | - | ||
| 49 | - // Draw export dialog | ||
| 50 | crate::ui::export_import::draw_export_dialog(state, ui); | 60 | crate::ui::export_import::draw_export_dialog(state, ui); |
| 51 | - | ||
| 52 | - // Draw import dialog | ||
| 53 | crate::ui::export_import::draw_import_dialog(state, ui); | 61 | crate::ui::export_import::draw_import_dialog(state, ui); |
| 54 | } | 62 | } |
| 55 | 63 | ||
| 56 | /// Draws the action buttons | 64 | /// Draws the action buttons |
| 57 | fn draw_action_buttons(state: &mut AppState, ui: &mut Ui) { | 65 | fn draw_action_buttons(state: &mut AppState, ui: &mut Ui) { |
| 58 | ui.horizontal(|ui| { | 66 | ui.horizontal(|ui| { |
| 59 | - if ui.button("📥 Ajouter un flux").clicked() { | 67 | + // Boutons principaux avec icônes et style amélioré |
| 68 | + if ui.button(egui::RichText::new("➕ Ajouter un flux").size(14.0)).clicked() { | ||
| 60 | state.clear_new_feed_form(); | 69 | state.clear_new_feed_form(); |
| 61 | state.show_add_feed_dialog = true; | 70 | state.show_add_feed_dialog = true; |
| 62 | } | 71 | } |
| 63 | 72 | ||
| 64 | - if ui.button("🔄 Synchroniser tous").clicked() { | 73 | + if ui.button(egui::RichText::new("🔄 Synchroniser tous").size(14.0)).clicked() { |
| 65 | sync_all_feeds(state); | 74 | sync_all_feeds(state); |
| 66 | } | 75 | } |
| 67 | 76 | ||
| 68 | ui.add_space(10.0); | 77 | ui.add_space(10.0); |
| 69 | 78 | ||
| 70 | - if ui.button("📤 Exporter").clicked() { | 79 | + // Boutons secondaires |
| 80 | + if ui.button(egui::RichText::new("📤 Exporter").size(14.0)).clicked() { | ||
| 71 | state.clear_export_import_error(); | 81 | state.clear_export_import_error(); |
| 72 | state.show_export_dialog = true; | 82 | state.show_export_dialog = true; |
| 73 | } | 83 | } |
| 74 | 84 | ||
| 75 | - if ui.button("📥 Importer").clicked() { | 85 | + if ui.button(egui::RichText::new("📥 Importer").size(14.0)).clicked() { |
| 76 | state.clear_export_import_error(); | 86 | state.clear_export_import_error(); |
| 77 | state.show_import_dialog = true; | 87 | state.show_import_dialog = true; |
| 78 | } | 88 | } |
| @@ -81,9 +91,16 @@ fn draw_action_buttons(state: &mut AppState, ui: &mut Ui) { | |||
| 81 | 91 | ||
| 82 | /// Draws the empty state message | 92 | /// Draws the empty state message |
| 83 | fn draw_empty_state(ui: &mut Ui) { | 93 | fn draw_empty_state(ui: &mut Ui) { |
| 84 | - ui.centered_and_justified(|ui| { | 94 | + ui.vertical_centered_justified(|ui| { |
| 85 | - ui.label(RichText::new("Aucun flux configuré").size(18.0)); | 95 | + ui.add_space(40.0); |
| 86 | - ui.label("Cliquez sur 'Ajouter un flux' pour commencer"); | 96 | + ui.label(egui::RichText::new("🎧").size(48.0)); |
| 97 | + ui.add_space(16.0); | ||
| 98 | + ui.label(egui::RichText::new("Bienvenue dans RSS Podcast Downloader").size(20.0).color(egui::Color32::from_rgb(220, 230, 245))); | ||
| 99 | + ui.add_space(8.0); | ||
| 100 | + ui.label(egui::RichText::new("Aucun flux configuré pour le moment").size(16.0).color(egui::Color32::from_rgb(180, 190, 210))); | ||
| 101 | + ui.add_space(16.0); | ||
| 102 | + ui.label(egui::RichText::new("👉 Cliquez sur '➕ Ajouter un flux' pour commencer").size(14.0).color(egui::Color32::from_rgb(150, 190, 255))); | ||
| 103 | + ui.add_space(40.0); | ||
| 87 | }); | 104 | }); |
| 88 | } | 105 | } |
| 89 | 106 | ||
modified
src/ui/status_bar.rs +55 -26 | @@ -7,37 +7,66 @@ use egui::{Align, Layout, Ui}; | ||
| 7 | 7 | |
| 8 | 8 | /// Draws the status bar at the bottom of the application |
| 9 | 9 | pub fn draw_status_bar(state: &mut AppState, ui: &mut Ui) { |
| 10 | - ui.horizontal(|ui| { | |
| 11 | - ui.spacing_mut().item_spacing.x = 0.0; | |
| 10 | + // Frame pour la barre de statut | |
| 11 | + egui::Frame::default() | |
| 12 | + .fill(egui::Color32::from_rgb(36, 39, 58)) | |
| 13 | + .stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(70, 78, 95))) | |
| 14 | + .rounding(egui::Rounding::same(4.0)) | |
| 15 | + .inner_margin(egui::Margin::symmetric(12.0, 8.0)) | |
| 16 | + .show(ui, |ui| { | |
| 17 | + ui.spacing_mut().item_spacing.x = 16.0; | |
| 12 | 18 | |
| 13 | - // Error message (highest priority) | |
| 14 | - if let Some(ref error) = state.error_message { | |
| 15 | - ui.colored_label(egui::Color32::RED, error); | |
| 16 | - if ui.button("✕").clicked() { | |
| 17 | - state.clear_error(); | |
| 19 | + // Error message (highest priority) avec icône | |
| 20 | + if let Some(ref error) = state.error_message { | |
| 21 | + let error_clone = error.clone(); | |
| 22 | + ui.horizontal(|ui| { | |
| 23 | + ui.label(egui::RichText::new("❌ ").color(egui::Color32::RED)); | |
| 24 | + ui.colored_label(egui::Color32::from_rgb(240, 100, 100), &error_clone); | |
| 25 | + if ui.button(egui::RichText::new("✕").size(14.0)).clicked() { | |
| 26 | + state.clear_error(); | |
| 27 | + } | |
| 28 | + }); | |
| 29 | + return; | |
| 18 | 30 | } |
| 19 | - return; | |
| 20 | - } | |
| 21 | 31 | |
| 22 | - // Info message | |
| 23 | - if let Some(ref info) = state.info_message { | |
| 24 | - ui.colored_label(egui::Color32::GREEN, info); | |
| 25 | - if ui.button("✕").clicked() { | |
| 26 | - state.clear_info(); | |
| 32 | + // Info message avec icône | |
| 33 | + if let Some(ref info) = state.info_message { | |
| 34 | + let info_clone = info.clone(); | |
| 35 | + ui.horizontal(|ui| { | |
| 36 | + ui.label(egui::RichText::new("ℹ️ ").color(egui::Color32::from_rgb(100, 200, 100))); | |
| 37 | + ui.colored_label(egui::Color32::from_rgb(100, 220, 100), &info_clone); | |
| 38 | + if ui.button(egui::RichText::new("✕").size(14.0)).clicked() { | |
| 39 | + state.clear_info(); | |
| 40 | + } | |
| 41 | + }); | |
| 42 | + return; | |
| 27 | 43 | } |
| 28 | - return; | |
| 29 | - } | |
| 30 | 44 | |
| 31 | - // Sync status | |
| 32 | - if state.sync_in_progress { | |
| 33 | - ui.colored_label(egui::Color32::YELLOW, "Synchronisation en cours..."); | |
| 34 | - } else { | |
| 35 | - ui.label("Prêt"); | |
| 36 | - } | |
| 45 | + // Sync status avec icône | |
| 46 | + let sync_in_progress = state.sync_in_progress; | |
| 47 | + let feed_count = state.feed_count(); | |
| 48 | + let enabled_count = state.enabled_feed_count(); | |
| 49 | + | |
| 50 | + ui.horizontal(|ui| { | |
| 51 | + if sync_in_progress { | |
| 52 | + ui.label(egui::RichText::new("🔄 ").color(egui::Color32::from_rgb(250, 200, 100))); | |
| 53 | + ui.colored_label(egui::Color32::from_rgb(250, 200, 100), "Synchronisation en cours..."); | |
| 54 | + } else { | |
| 55 | + ui.label(egui::RichText::new("✅ ").color(egui::Color32::from_rgb(100, 220, 100))); | |
| 56 | + ui.colored_label(egui::Color32::from_rgb(150, 200, 150), "Prêt"); | |
| 57 | + } | |
| 37 | 58 | |
| 38 | - // Feed count on the right | |
| 39 | - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | |
| 40 | - ui.label(format!("{} flux configurés", state.feed_count())); | |
| 59 | + // Feed count on the right avec icône | |
| 60 | + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | |
| 61 | + ui.label(egui::RichText::new(format!("📊 {} flux ", feed_count)) | |
| 62 | + .color(egui::Color32::from_rgb(160, 180, 200))); | |
| 63 | + | |
| 64 | + // Nombre de flux activés | |
| 65 | + if enabled_count < feed_count { | |
| 66 | + ui.label(egui::RichText::new(format!("| {} activés", enabled_count)) | |
| 67 | + .color(egui::Color32::from_rgb(140, 160, 180))); | |
| 68 | + } | |
| 69 | + }); | |
| 70 | + }); | |
| 41 | 71 | }); |
| 42 | - }); | |
| 43 | 72 | } |
| @@ -7,37 +7,66 @@ use egui::{Align, Layout, Ui}; | |||
| 7 | 7 | ||
| 8 | /// Draws the status bar at the bottom of the application | 8 | /// Draws the status bar at the bottom of the application |
| 9 | pub fn draw_status_bar(state: &mut AppState, ui: &mut Ui) { | 9 | pub fn draw_status_bar(state: &mut AppState, ui: &mut Ui) { |
| 10 | - ui.horizontal(|ui| { | 10 | + // Frame pour la barre de statut |
| 11 | - ui.spacing_mut().item_spacing.x = 0.0; | 11 | + egui::Frame::default() |
| 12 | + .fill(egui::Color32::from_rgb(36, 39, 58)) | ||
| 13 | + .stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(70, 78, 95))) | ||
| 14 | + .rounding(egui::Rounding::same(4.0)) | ||
| 15 | + .inner_margin(egui::Margin::symmetric(12.0, 8.0)) | ||
| 16 | + .show(ui, |ui| { | ||
| 17 | + ui.spacing_mut().item_spacing.x = 16.0; | ||
| 12 | 18 | ||
| 13 | - // Error message (highest priority) | 19 | + // Error message (highest priority) avec icône |
| 14 | - if let Some(ref error) = state.error_message { | 20 | + if let Some(ref error) = state.error_message { |
| 15 | - ui.colored_label(egui::Color32::RED, error); | 21 | + let error_clone = error.clone(); |
| 16 | - if ui.button("✕").clicked() { | 22 | + ui.horizontal(|ui| { |
| 17 | - state.clear_error(); | 23 | + ui.label(egui::RichText::new("❌ ").color(egui::Color32::RED)); |
| 24 | + ui.colored_label(egui::Color32::from_rgb(240, 100, 100), &error_clone); | ||
| 25 | + if ui.button(egui::RichText::new("✕").size(14.0)).clicked() { | ||
| 26 | + state.clear_error(); | ||
| 27 | + } | ||
| 28 | + }); | ||
| 29 | + return; | ||
| 18 | } | 30 | } |
| 19 | - return; | ||
| 20 | - } | ||
| 21 | 31 | ||
| 22 | - // Info message | 32 | + // Info message avec icône |
| 23 | - if let Some(ref info) = state.info_message { | 33 | + if let Some(ref info) = state.info_message { |
| 24 | - ui.colored_label(egui::Color32::GREEN, info); | 34 | + let info_clone = info.clone(); |
| 25 | - if ui.button("✕").clicked() { | 35 | + ui.horizontal(|ui| { |
| 26 | - state.clear_info(); | 36 | + ui.label(egui::RichText::new("ℹ️ ").color(egui::Color32::from_rgb(100, 200, 100))); |
| 37 | + ui.colored_label(egui::Color32::from_rgb(100, 220, 100), &info_clone); | ||
| 38 | + if ui.button(egui::RichText::new("✕").size(14.0)).clicked() { | ||
| 39 | + state.clear_info(); | ||
| 40 | + } | ||
| 41 | + }); | ||
| 42 | + return; | ||
| 27 | } | 43 | } |
| 28 | - return; | ||
| 29 | - } | ||
| 30 | 44 | ||
| 31 | - // Sync status | 45 | + // Sync status avec icône |
| 32 | - if state.sync_in_progress { | 46 | + let sync_in_progress = state.sync_in_progress; |
| 33 | - ui.colored_label(egui::Color32::YELLOW, "Synchronisation en cours..."); | 47 | + let feed_count = state.feed_count(); |
| 34 | - } else { | 48 | + let enabled_count = state.enabled_feed_count(); |
| 35 | - ui.label("Prêt"); | 49 | + |
| 36 | - } | 50 | + ui.horizontal(|ui| { |
| 51 | + if sync_in_progress { | ||
| 52 | + ui.label(egui::RichText::new("🔄 ").color(egui::Color32::from_rgb(250, 200, 100))); | ||
| 53 | + ui.colored_label(egui::Color32::from_rgb(250, 200, 100), "Synchronisation en cours..."); | ||
| 54 | + } else { | ||
| 55 | + ui.label(egui::RichText::new("✅ ").color(egui::Color32::from_rgb(100, 220, 100))); | ||
| 56 | + ui.colored_label(egui::Color32::from_rgb(150, 200, 150), "Prêt"); | ||
| 57 | + } | ||
| 37 | 58 | ||
| 38 | - // Feed count on the right | 59 | + // Feed count on the right avec icône |
| 39 | - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { | 60 | + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { |
| 40 | - ui.label(format!("{} flux configurés", state.feed_count())); | 61 | + ui.label(egui::RichText::new(format!("📊 {} flux ", feed_count)) |
| 62 | + .color(egui::Color32::from_rgb(160, 180, 200))); | ||
| 63 | + | ||
| 64 | + // Nombre de flux activés | ||
| 65 | + if enabled_count < feed_count { | ||
| 66 | + ui.label(egui::RichText::new(format!("| {} activés", enabled_count)) | ||
| 67 | + .color(egui::Color32::from_rgb(140, 160, 180))); | ||
| 68 | + } | ||
| 69 | + }); | ||
| 70 | + }); | ||
| 41 | }); | 71 | }); |
| 42 | - }); | ||
| 43 | } | 72 | } |