import 'package:flutter/material.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'app.dart'; import 'providers/notifications_provider.dart'; import 'utils/app_time.dart'; import 'utils/notification_permission.dart'; import 'services/notification_service.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await dotenv.load(fileName: '.env'); AppTime.initialize(location: 'Asia/Manila'); final supabaseUrl = dotenv.env['SUPABASE_URL'] ?? ''; final supabaseAnonKey = dotenv.env['SUPABASE_ANON_KEY'] ?? ''; if (supabaseUrl.isEmpty || supabaseAnonKey.isEmpty) { runApp(const _MissingConfigApp()); return; } await Supabase.initialize(url: supabaseUrl, anonKey: supabaseAnonKey); // on Android 13+ we must request POST_NOTIFICATIONS at runtime; without it // notifications are automatically denied and cannot be re‑enabled from the // system settings. The helper uses `permission_handler`. final granted = await ensureNotificationPermission(); if (!granted) { // we don’t block startup, but it’s worth logging so developers notice. A // real app might show a dialog pointing the user to settings. // debugPrint('notification permission not granted'); } // initialize the local notifications plugin so we can post alerts later await NotificationService.initialize( onDidReceiveNotificationResponse: (response) { // handle user tapping a notification; the payload format is up to us final payload = response.payload; if (payload != null && payload.startsWith('ticket:')) { // ignore if context not mounted; we might use a navigator key in real // app, but keep this simple for now // TODO: navigate to ticket/task as appropriate } }, ); runApp( ProviderScope( observers: [NotificationSoundObserver()], child: const TasqApp(), ), ); } class NotificationSoundObserver extends ProviderObserver { static final AudioPlayer _player = AudioPlayer(); @override void didUpdateProvider( ProviderBase provider, Object? previousValue, Object? newValue, ProviderContainer container, ) { if (provider == unreadNotificationsCountProvider) { final prev = previousValue as int?; final next = newValue as int?; if (prev != null && next != null && next > prev) { _player.play(AssetSource('tasq_notification.wav')); // also post a system notification so the user sees it outside the app NotificationService.show( id: DateTime.now().millisecondsSinceEpoch ~/ 1000, title: 'New notifications', body: 'You have $next unread notifications.', ); } } } } class _MissingConfigApp extends StatelessWidget { const _MissingConfigApp(); @override Widget build(BuildContext context) { return const MaterialApp( home: Scaffold( body: Center( child: Padding( padding: EdgeInsets.all(24), child: Text( 'Missing SUPABASE_URL or SUPABASE_ANON_KEY. ' 'Provide them in the .env file.', textAlign: TextAlign.center, ), ), ), ), ); } }