feat(player): sleep timer and delayed start via long-press on play/pause (#270)

Add scheduled pause and resume timers in the player store, cleared on stop
and track changes. Long-press opens a compact preset modal anchored above
the transport row; one-tap presets plus custom minutes. Portaled countdown
badge on the play button; clear the long-press click guard when the modal
closes so the first play/pause click works after scheduling.
This commit is contained in:
cucadmuh
2026-04-23 00:32:44 +03:00
committed by GitHub
parent c5dfabf739
commit 624ce56faf
17 changed files with 942 additions and 20 deletions
+18
View File
@@ -1126,6 +1126,24 @@ export const deTranslation = {
prev: 'Vorheriger Titel',
play: 'Play',
pause: 'Pause',
delayModalTitle: 'Timer',
delayPauseSection: 'Pause nach',
delayStartSection: 'Start nach',
delaySchedulePause: 'Pause planen',
delayScheduleStart: 'Start planen',
delayCancelPause: 'Pause-Timer abbrechen',
delayCancelStart: 'Start-Timer abbrechen',
delayInactivePause: 'Nur während der Wiedergabe verfügbar.',
delayInactiveStart: 'Nur in Pause mit geladenem Titel oder Radio.',
delayCustomMinutes: 'Eigene Dauer (Minuten)',
delayCustomPlaceholder: 'z. B. 2,5',
closeDelayModal: 'Schließen',
delayIn: 'in',
delayFmtSec: '{{n}} s',
delayFmtMin: '{{n}} Min',
delayFmtHr: '{{n}} Std',
delayCancel: 'Abbrechen',
delayApply: 'Übernehmen',
next: 'Nächster Titel',
repeat: 'Wiederholen',
repeatOff: 'Aus',
+18
View File
@@ -1128,6 +1128,24 @@ export const enTranslation = {
prev: 'Previous Track',
play: 'Play',
pause: 'Pause',
delayModalTitle: 'Timer',
delayPauseSection: 'Pause after',
delayStartSection: 'Start after',
delaySchedulePause: 'Schedule pause',
delayScheduleStart: 'Schedule start',
delayCancelPause: 'Cancel pause timer',
delayCancelStart: 'Cancel start timer',
delayInactivePause: 'Available only while something is playing.',
delayInactiveStart: 'Available when paused with a track or radio loaded.',
delayCustomMinutes: 'Custom delay (minutes)',
delayCustomPlaceholder: 'e.g. 2.5',
closeDelayModal: 'Close',
delayIn: 'in',
delayFmtSec: '{{n}}s',
delayFmtMin: '{{n}} min',
delayFmtHr: '{{n}} h',
delayCancel: 'Cancel',
delayApply: 'Apply',
next: 'Next Track',
repeat: 'Repeat',
repeatOff: 'Off',
+18
View File
@@ -1119,6 +1119,24 @@ export const esTranslation = {
prev: 'Pista Anterior',
play: 'Reproducir',
pause: 'Pausa',
delayModalTitle: 'Temporizador',
delayPauseSection: 'Pausar después de',
delayStartSection: 'Iniciar después de',
delaySchedulePause: 'Programar pausa',
delayScheduleStart: 'Programar inicio',
delayCancelPause: 'Cancelar temporizador de pausa',
delayCancelStart: 'Cancelar temporizador de inicio',
delayInactivePause: 'Solo disponible durante la reproducción.',
delayInactiveStart: 'Solo en pausa con pista o radio cargada.',
delayCustomMinutes: 'Retraso personalizado (minutos)',
delayCustomPlaceholder: 'p. ej. 2,5',
closeDelayModal: 'Cerrar',
delayIn: 'en',
delayFmtSec: '{{n}} s',
delayFmtMin: '{{n}} min',
delayFmtHr: '{{n}} h',
delayCancel: 'Cancelar',
delayApply: 'Aplicar',
next: 'Pista Siguiente',
repeat: 'Repetir',
repeatOff: 'Apagado',
+18
View File
@@ -1114,6 +1114,24 @@ export const frTranslation = {
prev: 'Piste précédente',
play: 'Lecture',
pause: 'Pause',
delayModalTitle: 'Minuteur',
delayPauseSection: 'Pause après',
delayStartSection: 'Démarrage après',
delaySchedulePause: 'Programmer la pause',
delayScheduleStart: 'Programmer le démarrage',
delayCancelPause: 'Annuler le minuteur de pause',
delayCancelStart: 'Annuler le minuteur de démarrage',
delayInactivePause: 'Disponible uniquement pendant la lecture.',
delayInactiveStart: 'Disponible en pause avec une piste ou une radio chargée.',
delayCustomMinutes: 'Délai personnalisé (minutes)',
delayCustomPlaceholder: 'ex. 2,5',
closeDelayModal: 'Fermer',
delayIn: 'dans',
delayFmtSec: '{{n}} s',
delayFmtMin: '{{n}} min',
delayFmtHr: '{{n}} h',
delayCancel: 'Annuler',
delayApply: 'Appliquer',
next: 'Piste suivante',
repeat: 'Répéter',
repeatOff: 'Désactivé',
+18
View File
@@ -1113,6 +1113,24 @@ export const nbTranslation = {
prev: 'Forrige spor',
play: 'Spill av',
pause: 'Pause',
delayModalTitle: 'Tidsur',
delayPauseSection: 'Pause etter',
delayStartSection: 'Start etter',
delaySchedulePause: 'Planlegg pause',
delayScheduleStart: 'Planlegg start',
delayCancelPause: 'Avbryt pause-timer',
delayCancelStart: 'Avbryt start-timer',
delayInactivePause: 'Kun tilgjengelig under avspilling.',
delayInactiveStart: 'Kun i pause med spor eller radio lastet.',
delayCustomMinutes: 'Egen forsinkelse (minutter)',
delayCustomPlaceholder: 'f.eks. 2,5',
closeDelayModal: 'Lukk',
delayIn: 'om',
delayFmtSec: '{{n}} s',
delayFmtMin: '{{n}} min',
delayFmtHr: '{{n}} t',
delayCancel: 'Avbryt',
delayApply: 'Bruk',
next: 'Neste spor',
repeat: 'Gjenta',
repeatOff: 'Av',
+18
View File
@@ -1113,6 +1113,24 @@ export const nlTranslation = {
prev: 'Vorig nummer',
play: 'Afspelen',
pause: 'Pauzeren',
delayModalTitle: 'Timer',
delayPauseSection: 'Pauzeren na',
delayStartSection: 'Starten na',
delaySchedulePause: 'Pauze plannen',
delayScheduleStart: 'Start plannen',
delayCancelPause: 'Pauze-timer annuleren',
delayCancelStart: 'Start-timer annuleren',
delayInactivePause: 'Alleen beschikbaar tijdens afspelen.',
delayInactiveStart: 'Alleen in pauze met een geladen nummer of radio.',
delayCustomMinutes: 'Aangepaste vertraging (minuten)',
delayCustomPlaceholder: 'bijv. 2,5',
closeDelayModal: 'Sluiten',
delayIn: 'over',
delayFmtSec: '{{n}} s',
delayFmtMin: '{{n}} min',
delayFmtHr: '{{n}} u',
delayCancel: 'Annuleren',
delayApply: 'Toepassen',
next: 'Volgend nummer',
repeat: 'Herhalen',
repeatOff: 'Uit',
+18
View File
@@ -1191,6 +1191,24 @@ export const ruTranslation = {
prev: 'Предыдущий трек',
play: 'Играть',
pause: 'Пауза',
delayModalTitle: 'Таймер',
delayPauseSection: 'Пауза через',
delayStartSection: 'Старт через',
delaySchedulePause: 'Запланировать паузу',
delayScheduleStart: 'Запланировать старт',
delayCancelPause: 'Отменить таймер паузы',
delayCancelStart: 'Отменить таймер старта',
delayInactivePause: 'Доступно только во время воспроизведения.',
delayInactiveStart: 'Доступно на паузе при загруженном треке или радио.',
delayCustomMinutes: 'Своя задержка (минуты)',
delayCustomPlaceholder: 'напр. 2.5',
closeDelayModal: 'Закрыть',
delayIn: 'через',
delayFmtSec: '{{n}} с',
delayFmtMin: '{{n}} мин',
delayFmtHr: '{{n}} ч',
delayCancel: 'Отмена',
delayApply: 'Применить',
next: 'Следующий трек',
repeat: 'Повтор',
repeatOff: 'Выкл.',
+18
View File
@@ -1108,6 +1108,24 @@ export const zhTranslation = {
prev: '上一首',
play: '播放',
pause: '暂停',
delayModalTitle: '定时',
delayPauseSection: '多久后暂停',
delayStartSection: '多久后开始',
delaySchedulePause: '安排暂停',
delayScheduleStart: '安排开始',
delayCancelPause: '取消暂停定时',
delayCancelStart: '取消开始定时',
delayInactivePause: '仅在正在播放时可用。',
delayInactiveStart: '仅在暂停且已加载曲目或电台时可用。',
delayCustomMinutes: '自定义延迟(分钟)',
delayCustomPlaceholder: '例如 2.5',
closeDelayModal: '关闭',
delayIn: '还有',
delayFmtSec: '{{n}} 秒',
delayFmtMin: '{{n}} 分钟',
delayFmtHr: '{{n}} 小时',
delayCancel: '取消',
delayApply: '应用',
next: '下一首',
repeat: '重复',
repeatOff: '关闭',