import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/timezone.dart' as tz; import 'package:timezone/data/latest.dart' as tz; import 'package:flutter_timezone/flutter_timezone.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../services/google_calendar_service.dart'; class NotificationService { static final NotificationService _instance = NotificationService._internal(); factory NotificationService() => _instance; NotificationService._internal(); final FlutterLocalNotificationsPlugin _plugin = FlutterLocalNotificationsPlugin(); bool _initialized = false; static const _channel = AndroidNotificationChannel( 'shooting_app_events', 'Event Reminders', description: 'Notifications for upcoming shooting events', importance: Importance.max, playSound: true, enableVibration: true, ); NotificationDetails get _notifDetails => const NotificationDetails( android: AndroidNotificationDetails( 'shooting_app_events', 'Event Reminders', channelDescription: 'Notifications for upcoming shooting events', importance: Importance.max, priority: Priority.high, icon: '@drawable/ic_stat_icon_white', ), iOS: DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ), ); Future initialize() async { if (_initialized) return; _initialized = true; // set early to prevent re-entry try { tz.initializeTimeZones(); final String timezoneName = await FlutterTimezone.getLocalTimezone(); tz.setLocalLocation(tz.getLocation(timezoneName)); } catch (_) {} try { const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); const iosSettings = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, ); const initSettings = InitializationSettings(android: androidSettings, iOS: iosSettings); await _plugin.initialize(initSettings); final androidPlugin = _plugin.resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>(); await androidPlugin?.createNotificationChannel(_channel); await requestPermissions(); } catch (_) {} } Future requestPermissions() async { final android = _plugin.resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>(); final ios = _plugin.resolvePlatformSpecificImplementation< IOSFlutterLocalNotificationsPlugin>(); bool granted = false; if (android != null) { granted = await android.requestNotificationsPermission() ?? false; } if (ios != null) { granted = await ios.requestPermissions(alert: true, badge: true, sound: true) ?? false; } return granted; } Future cancelAll() async { await _plugin.cancelAll(); } Future scheduleEventNotifications(List events) async { try { await initialize(); final prefs = await SharedPreferences.getInstance(); final hours = prefs.getInt('notificationHours_single') ?? 1; final notifyDezuranje = prefs.getBool('notifyDezuranje') ?? true; final notifySestanek = prefs.getBool('notifySestanek') ?? true; final notifyTekma = prefs.getBool('notifyTekma') ?? true; final isSlovenian = (prefs.getString('language') ?? 'English') == 'Slovenian'; final now = DateTime.now(); await _plugin.cancelAll(); int notifId = 0; for (final event in events) { if (event.startTime == null) continue; final summary = (event.summary ?? '').toLowerCase(); final isCompetition = summary.contains('liga') || summary.contains('tekmovanje'); final isSestanek = summary.contains('sestanek'); final isDezuranje = !isCompetition && !isSestanek; if (isCompetition && !notifyTekma) continue; if (isSestanek && !notifySestanek) continue; if (isDezuranje && !notifyDezuranje) continue; final notifyAt = event.startTime!.subtract(Duration(hours: hours)); if (notifyAt.isAfter(now)) { await _scheduleNotification( id: notifId++, title: event.summary ?? 'Event', body: isSlovenian ? (hours == 1 ? 'Začne se čez 1 uro' : 'Začne se čez $hours ur') : (hours == 1 ? 'Starts in 1 hour' : 'Starts in $hours hours'), scheduledDate: notifyAt, ); } } } catch (_) {} } Future scheduleTestNotification() async { try { await initialize(); final prefs = await SharedPreferences.getInstance(); final isSlovenian = (prefs.getString('language') ?? 'English') == 'Slovenian'; await _plugin.show( 9999, isSlovenian ? 'Testno obvestilo' : 'Test Notification', isSlovenian ? 'Obvestila delujejo!' : 'Notifications are working!', _notifDetails, ); final in10s = DateTime.now().add(const Duration(seconds: 10)); await _scheduleNotification( id: 9998, title: isSlovenian ? 'Načrtovano testno obvestilo' : 'Scheduled Test', body: isSlovenian ? 'Načrtovana obvestila delujejo!' : 'Scheduled notifications are working!', scheduledDate: in10s, ); } catch (_) {} } Future _scheduleNotification({ required int id, required String title, required String body, required DateTime scheduledDate, }) async { final tzDate = tz.TZDateTime.from(scheduledDate, tz.local); await _plugin.zonedSchedule( id, title, body, tzDate, _notifDetails, androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle, uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, ); } }