diff --git a/Blackboard/Done/large_cover.md b/Blackboard/Done/large_cover.md new file mode 100644 index 00000000..5291f083 --- /dev/null +++ b/Blackboard/Done/large_cover.md @@ -0,0 +1,63 @@ +# Tâche : Amélioration de l'overlay de couverture d'album + +**Crate concernée** : pmoapp/webapp + +**Statut** : Terminée + +--- + +## Demande initiale + +### Point 1 : Métadonnées et progress bar +Quand on clique sur l'image de couverture de l'album, elle s'agrandit pour prendre tout l'espace. Par défaut les métadonnées sont cachées et si on clique sur l'écran, il y a un effet toggle. Il fallait que par défaut elles soient visibles, avec une progress bar draggable dans une petite boîte au fond gris transparent avec un effet glass metal à la macOS. + +### Point 2 : Ergonomie mobile +Le bouton de fermeture en haut à droite n'était pas ergonomique sur mobile où les doigts sont plutôt en bas de l'écran. + +--- + +## Synthèse des modifications + +### Fichier modifié +- `pmoapp/webapp/src/components/pmocontrol/CurrentTrack.vue` + +### Point 1 : Résolu +- Métadonnées visibles par défaut à l'ouverture +- Progress bar interactive ajoutée dans le panneau des métadonnées (souris + tactile) +- Style glassmorphism : fond transparent 35%, blur 30px, bordure subtile, ombre +- Panneau réduit à 70% de largeur pour un look plus élégant + +### Point 2 : Résolu +- **Swipe down** pour fermer l'overlay sur tactile (seuil 100px) +- Feedback visuel pendant le geste (translation + fade) +- Bouton X conservé pour desktop +- Blocage du pull-to-refresh Android (`overscroll-behavior: contain`) + +### Corrections additionnelles +- Support tactile de la progress bar principale (hors overlay) +- Utilisation de `100dvh` au lieu de `100vh` pour compatibilité avec les barres de navigation mobiles (Brave, Safari, Chrome) +- Corrections TypeScript pour les événements tactiles + +--- + +## Éléments techniques clés + +### Swipe down +```typescript +const swipeStartY = ref(0); +const swipeCurrentY = ref(0); +const isSwiping = ref(false); +const swipeThreshold = 100; +``` +Listeners ajoutés sur `document` pour capturer le mouvement même sur les éléments enfants. + +### CSS mobile +```css +.cover-overlay { + overscroll-behavior: contain; + touch-action: none; +} +.cover-overlay-content { + height: calc(100dvh - 32px); +} +``` diff --git a/Blackboard/Report/large_cover.md b/Blackboard/Report/large_cover.md new file mode 100644 index 00000000..688c1f7b --- /dev/null +++ b/Blackboard/Report/large_cover.md @@ -0,0 +1,89 @@ +# Rapport : Amélioration de l'overlay de couverture d'album + +## Résumé + +Amélioration de l'interface de l'overlay affichant la couverture d'album en grand dans le Control Point PMOMusic, avec focus sur l'ergonomie mobile. + +## Fichier modifié + +- `pmoapp/webapp/src/components/pmocontrol/CurrentTrack.vue` + +## Modifications effectuées + +### Point 1 : Métadonnées visibles par défaut + Progress bar + +**Problème initial** : Les métadonnées étaient cachées par défaut à l'ouverture de l'overlay. + +**Solutions implémentées** : + +1. **Métadonnées visibles par défaut** : `showMetadata.value = true` dans `openCoverOverlay()` + +2. **Ajout de la progress bar** dans le panneau des métadonnées : + - Barre de progression interactive avec thumb draggable + - Affichage temps écoulé / durée totale + - Support souris et tactile complet + - Style glassmorphism (fond transparent 35%, blur, bordure subtile) + +3. **Panneau plus discret** : + - Transparence augmentée (35% au lieu de 60%) + - Largeur réduite (70% de l'écran avec `left: 15%; right: 15%`) + +### Point 2 : Fermeture ergonomique sur mobile + +**Problème initial** : Bouton X en haut à droite difficile d'accès au pouce. + +**Solutions implémentées** : + +1. **Swipe down pour fermer** : + - Geste naturel vers le bas pour fermer l'overlay + - Feedback visuel (translation + diminution opacité) + - Seuil de 100px pour déclencher la fermeture + - Listeners sur `document` pour capturer le mouvement même sur les éléments enfants + +2. **Bouton X conservé** pour desktop/souris + +3. **Blocage du pull-to-refresh Android** : + - `overscroll-behavior: contain` + - `touch-action: none` sur l'overlay + +### Corrections supplémentaires + +1. **Support tactile progress bar principale** : Ajout de `@touchstart` sur la progress bar hors overlay + +2. **Compatibilité viewport mobile** : Remplacement de `100vh` par `100dvh` (dynamic viewport height) pour éviter que la barre du navigateur masque le contenu + +3. **Corrections TypeScript** : Vérifications `if (!touch) return` pour les événements tactiles + +## Détails techniques + +### Nouvelles variables réactives + +```typescript +const swipeStartY = ref(0); +const swipeCurrentY = ref(0); +const isSwiping = ref(false); +const swipeThreshold = 100; +``` + +### Nouvelles fonctions + +- `handleOverlayTouchStart()` : Initialise le swipe et ajoute les listeners document +- `handleSwipeTouchMove()` : Suit le mouvement du doigt +- `handleSwipeTouchEnd()` : Ferme si seuil atteint, nettoie les listeners +- `handleOverlayProgressBarTouchStart()` : Gestion tactile du seek (réutilisée par les deux progress bars) +- `swipeOffset` / `swipeOpacity` : Computed pour le feedback visuel + +### Nouveaux styles CSS + +- `.overlay-metadata-text` : Conteneur du texte +- `.overlay-progress-section` : Section progress bar +- `.overlay-progress-bar` / `-fill` / `-thumb` : Style glassmorphism +- `.overlay-time-display` : Affichage temps +- `.cover-overlay-content.swiping` : État pendant le swipe +- `overscroll-behavior: contain` + `touch-action: none` : Blocage pull-to-refresh +- `100dvh` : Viewport dynamique pour mobile + +### Responsive + +- Mobile (< 768px) : Progress bar 10px, thumb 28px +- Mode kiosque (800x600) : Progress bar 6px diff --git a/Blackboard/Todo/config_ext.md b/Blackboard/ToDiscuss/config_ext.md similarity index 100% rename from Blackboard/Todo/config_ext.md rename to Blackboard/ToDiscuss/config_ext.md diff --git a/Blackboard/Todo/music_source.md b/Blackboard/ToDiscuss/music_source.md similarity index 100% rename from Blackboard/Todo/music_source.md rename to Blackboard/ToDiscuss/music_source.md diff --git a/Cargo.lock b/Cargo.lock index 4497244d..f0a2c995 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "PMOMusic" -version = "0.3.8" +version = "0.3.9" dependencies = [ "axum 0.8.7", "console-subscriber", diff --git a/PMOMusic/Cargo.toml b/PMOMusic/Cargo.toml index 71f7bba4..075035f5 100644 --- a/PMOMusic/Cargo.toml +++ b/PMOMusic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "PMOMusic" -version = "0.3.8" +version = "0.3.9" edition = "2024" [dependencies] diff --git a/pmoapp/webapp/src/components/pmocontrol/CurrentTrack.vue b/pmoapp/webapp/src/components/pmocontrol/CurrentTrack.vue index 548bd5a0..a6f3660d 100644 --- a/pmoapp/webapp/src/components/pmocontrol/CurrentTrack.vue +++ b/pmoapp/webapp/src/components/pmocontrol/CurrentTrack.vue @@ -24,7 +24,7 @@ const showMetadata = ref(false); function openCoverOverlay() { if (hasCover.value) { showCoverOverlay.value = true; - showMetadata.value = false; // Reset metadata visibility when opening + showMetadata.value = true; // Afficher les métadonnées par défaut } } @@ -186,6 +186,148 @@ async function handleProgressBarClick(event: MouseEvent) { isSeeking.value = false; } } + +// Fonctions pour la progress bar de l'overlay (réutilisent la même logique) +function handleOverlayProgressBarClick(event: MouseEvent) { + handleProgressBarClick(event); +} + +function handleOverlayProgressBarMouseDown(event: MouseEvent) { + handleProgressBarMouseDown(event); +} + +// Gestion tactile pour la progress bar de l'overlay +function handleOverlayProgressBarTouchStart(event: TouchEvent) { + const duration = state.value?.duration_ms; + if (!duration || duration === 0 || isSeeking.value) return; + + const progressBar = event.currentTarget as HTMLElement; + const touch = event.touches[0]; + if (!touch) return; + + isDragging.value = true; + dragProgress.value = calculateProgressFromX(touch.clientX, progressBar); + + const handleTouchMove = (e: TouchEvent) => { + if (!isDragging.value) return; + const moveTouch = e.touches[0]; + if (!moveTouch) return; + dragProgress.value = calculateProgressFromX( + moveTouch.clientX, + progressBar, + ); + }; + + const handleTouchEnd = async (e: TouchEvent) => { + if (!isDragging.value) return; + + const endTouch = e.changedTouches[0]; + if (!endTouch) return; + + const finalPercent = calculateProgressFromX( + endTouch.clientX, + progressBar, + ); + const newPositionMs = (finalPercent / 100) * duration; + const newPositionSeconds = Math.floor(newPositionMs / 1000); + + dragProgress.value = finalPercent; + seekTargetMs.value = newPositionMs; + isDragging.value = false; + + try { + isSeeking.value = true; + await api.seekTo(props.rendererId, newPositionSeconds); + } catch (error) { + uiStore.notifyError( + `Impossible de seek: ${error instanceof Error ? error.message : "Erreur inconnue"}`, + ); + seekTargetMs.value = null; + dragProgress.value = 0; + } finally { + isSeeking.value = false; + } + + document.removeEventListener("touchmove", handleTouchMove); + document.removeEventListener("touchend", handleTouchEnd); + }; + + document.addEventListener("touchmove", handleTouchMove, { passive: true }); + document.addEventListener("touchend", handleTouchEnd); +} + +// Gestion du swipe down pour fermer l'overlay +const swipeStartY = ref(0); +const swipeCurrentY = ref(0); +const isSwiping = ref(false); +const swipeThreshold = 100; // Distance minimale pour fermer + +function handleOverlayTouchStart(event: TouchEvent) { + const touch = event.touches[0]; + if (!touch) return; + swipeStartY.value = touch.clientY; + swipeCurrentY.value = touch.clientY; + isSwiping.value = true; + + // Ajouter les listeners sur le document pour capturer tous les mouvements + document.addEventListener("touchmove", handleSwipeTouchMove, { + passive: true, + }); + document.addEventListener("touchend", handleSwipeTouchEnd); + document.addEventListener("touchcancel", handleSwipeTouchEnd); +} + +function handleSwipeTouchMove(event: TouchEvent) { + if (!isSwiping.value) return; + const touch = event.touches[0]; + if (!touch) return; + swipeCurrentY.value = touch.clientY; +} + +function handleSwipeTouchEnd() { + if (!isSwiping.value) return; + + const swipeDistance = swipeCurrentY.value - swipeStartY.value; + + // Si swipe vers le bas suffisant, fermer l'overlay + if (swipeDistance > swipeThreshold) { + closeCoverOverlay(); + } + + isSwiping.value = false; + swipeStartY.value = 0; + swipeCurrentY.value = 0; + + // Retirer les listeners + document.removeEventListener("touchmove", handleSwipeTouchMove); + document.removeEventListener("touchend", handleSwipeTouchEnd); + document.removeEventListener("touchcancel", handleSwipeTouchEnd); +} + +// Ancienne fonction conservée pour compatibilité template (non utilisée) +function handleOverlayTouchMove(event: TouchEvent) { + handleSwipeTouchMove(event); +} + +function handleOverlayTouchEnd() { + handleSwipeTouchEnd(); +} + +// Calcul du décalage visuel pendant le swipe +const swipeOffset = computed(() => { + if (!isSwiping.value) return 0; + const offset = swipeCurrentY.value - swipeStartY.value; + // Ne permettre que le swipe vers le bas (positif) + return Math.max(0, offset); +}); + +// Opacité qui diminue pendant le swipe +const swipeOpacity = computed(() => { + if (!isSwiping.value) return 1; + const offset = swipeOffset.value; + // Réduire l'opacité progressivement + return Math.max(0.3, 1 - offset / 300); +});