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 Warning | 20:00 если не учился | "Сохрани streak!" |
| Weekly Summary | Воскресенье | "На этой неделе: 5 уроков" |
Локальные
| Тип | Когда | Содержание |
|---|---|---|
| Streak Reminder | 19: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, // Немедленно
});
}