feat(meals): вкладка «Питание» — напоминания о еде по времени суток
Новая модель Meal — напоминание по настенным часам (time HH:MM + дни недели), в отличие от interval-based Exercise. Отдельная вкладка «Питание» с пресетами быстрого добавления (Завтрак/Обед/Ужин/Перекус). - shared: тип Meal, meals в AppState, nextMealOccurrence (DST-safe), SAMPLE_MEALS, MEAL_PRESETS; IPC-каналы meal:* + evtFireMeal - main: валидация (строгая HH:MM-проверка диапазона), store-мутаторы с пересчётом nextFireAt, scheduler.checkDueMeals (гейт только globalEnabled, grace-окно 120с, игнор тихих часов/ВКС), notifications.fireMealReminder, IPC-хендлеры - renderer: вкладка Meals + MealEditor (время/дни/иконка), MealReminder в окне напоминания (Поел/Отложить, TTS), пункт в Sidebar, маршрут, i18n RU/EN, иконки UtensilsCrossed/Soup - persistence: meals additive (без bump схемы — старые state'ы получают []) - +24 теста (203 -> 227): nextMealOccurrence, валидаторы приёмов пищи, scheduler meal-gating (вкл/выкл, grace, игнор тихих часов) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { Notification, app } from 'electron'
|
||||
import type { Exercise, MatchSummary, NotificationMode } from '@shared/types'
|
||||
import type {
|
||||
Exercise,
|
||||
MatchSummary,
|
||||
Meal,
|
||||
NotificationMode
|
||||
} from '@shared/types'
|
||||
import { IPC } from '@shared/ipc'
|
||||
import {
|
||||
createReminderWindow,
|
||||
@@ -12,6 +17,35 @@ export function fireReminder(exercise: Exercise, mode: NotificationMode): void {
|
||||
if (mode === 'modal' || mode === 'both') showModal(exercise)
|
||||
}
|
||||
|
||||
export function fireMealReminder(meal: Meal, mode: NotificationMode): void {
|
||||
if (mode === 'toast' || mode === 'both') showMealToast(meal)
|
||||
if (mode === 'modal' || mode === 'both') showMealModal(meal)
|
||||
}
|
||||
|
||||
function showMealToast(meal: Meal): void {
|
||||
if (!Notification.isSupported()) return
|
||||
const n = new Notification({
|
||||
title: app.getName(),
|
||||
body: meal.name,
|
||||
silent: false
|
||||
})
|
||||
n.on('click', () => showReminderWindow())
|
||||
n.show()
|
||||
}
|
||||
|
||||
function showMealModal(meal: Meal): void {
|
||||
const win = createReminderWindow()
|
||||
const send = (): void => {
|
||||
win.webContents.send(IPC.evtFireMeal, meal)
|
||||
}
|
||||
if (win.webContents.isLoading()) {
|
||||
win.webContents.once('did-finish-load', send)
|
||||
} else {
|
||||
send()
|
||||
}
|
||||
showReminderWindow()
|
||||
}
|
||||
|
||||
export function fireMatchSummary(summary: MatchSummary): void {
|
||||
if (Notification.isSupported()) {
|
||||
const totalReps = summary.results.reduce((s, r) => s + r.reps, 0)
|
||||
|
||||
Reference in New Issue
Block a user