Перейти к основному содержимому

Notifications

Push-уведомления и локальные напоминания.

Структура

src/features/notifications/
├── register-push.ts # registerForPushNotifications()
├── notification-handler.ts # setupNotificationResponseHandler()
├── streak-reminder.ts # scheduleStreakReminder()
└── index.ts

Push Notifications

Регистрация

// register-push.ts
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { Platform } from 'react-native';

export async function registerForPushNotifications() {
if (!Device.isDevice) {
console.log('Push notifications require a physical device');
return null;
}

// Запросить разрешения
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;

if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}

if (finalStatus !== 'granted') {
console.log('Push notification permission denied');
return null;
}

// Получить Expo Push Token
const token = await Notifications.getExpoPushTokenAsync({
projectId: process.env.EXPO_PUBLIC_PROJECT_ID,
});

// Android: настроить канал
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
});
}

return token.data;
}

Сохранение токена

// При получении токена — сохранить на сервер
async function savePushToken(token: string, userId: string) {
await supabase
.from('user_push_tokens')
.upsert({
user_id: userId,
token,
platform: Platform.OS,
});
}

Notification Handler

Обработка нажатий на уведомления:

// notification-handler.ts
import * as Notifications from 'expo-notifications';
import { router } from 'expo-router';

export function setupNotificationResponseHandler() {
const subscription = Notifications.addNotificationResponseReceivedListener(
(response) => {
const data = response.notification.request.content.data;

// Deep linking
if (data.courseId) {
router.push(`/course/${data.courseId}`);
} else if (data.lessonId) {
router.push(`/lesson/${data.lessonId}`);
} else if (data.screen) {
router.push(data.screen);
}
}
);

return () => subscription.remove();
}

Использование в Root Layout

// app/_layout.tsx
export default function RootLayout() {
useEffect(() => {
registerForPushNotifications().then(token => {
if (token && session?.user.id) {
savePushToken(token, session.user.id);
}
});

const cleanup = setupNotificationResponseHandler();
return cleanup;
}, [session]);
}

Streak Reminders

Локальные напоминания для сохранения streak:

// streak-reminder.ts
import * as Notifications from 'expo-notifications';

export async function scheduleStreakReminder() {
// Отменить предыдущие
await Notifications.cancelAllScheduledNotificationsAsync();

// Запланировать на 19:00 каждый день
await Notifications.scheduleNotificationAsync({
content: {
title: 'Не забудь про обучение! 🔥',
body: 'Сохрани свой streak — пройди хотя бы один урок сегодня',
data: { screen: '/(app)/(tabs)' },
},
trigger: {
type: 'daily',
hour: 19,
minute: 0,
},
});
}

Управление напоминаниями

// settings/notifications.tsx
function NotificationsSettings() {
const { notificationsEnabled, setNotificationsEnabled } = useSettingsStore();

const handleToggle = async (enabled: boolean) => {
setNotificationsEnabled(enabled);

if (enabled) {
await scheduleStreakReminder();
} else {
await Notifications.cancelAllScheduledNotificationsAsync();
}
};

return (
<Switch value={notificationsEnabled} onValueChange={handleToggle} />
);
}

Типы уведомлений

Push (с сервера)

ТипКогдаСодержание
New Course ReadyКурс создан"Ваш курс X готов!"
Streak Warning20:00 если не учился"Сохрани streak!"
Weekly SummaryВоскресенье"На этой неделе: 5 уроков"

Локальные

ТипКогдаСодержание
Streak Reminder19:00 ежедневно"Не забудь про обучение!"
Lesson ReminderПо расписанию"Время для урока"

Zustand Store

В src/stores/settings.ts:

interface SettingsState {
notificationsEnabled: boolean;
reminderTime: string; // "19:00"
setNotificationsEnabled: (enabled: boolean) => void;
setReminderTime: (time: string) => void;
}

Permissions

// Проверка статуса разрешений
async function checkNotificationPermissions() {
const { status } = await Notifications.getPermissionsAsync();
return status === 'granted';
}

// Открыть настройки если denied
import { Linking } from 'react-native';

function openSettings() {
Linking.openSettings();
}

Testing

// Отправить тестовое уведомление
async function sendTestNotification() {
await Notifications.scheduleNotificationAsync({
content: {
title: 'Тест',
body: 'Это тестовое уведомление',
},
trigger: null, // Немедленно
});
}