uaestats_fe/lib/main.dart
2026-05-29 17:45:19 +05:30

295 lines
9.9 KiB
Dart
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:io';
// import 'package:firebase_core/firebase_core.dart';
// import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:appsflyer_sdk/appsflyer_sdk.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:app_links/app_links.dart';
import 'package:uae_stat/config/connectivity_provider.dart'
as legacy_connectivity;
import 'package:uae_stat/config/injector.dart';
import 'package:uae_stat/lib/core/routing/app_router.dart';
import 'package:uae_stat/lib/core/theme/app_theme.dart';
import 'package:uae_stat/presentation/components/indicators/locale_provider.dart';
import 'package:uae_stat/l10n/app_localizations.dart';
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
if (!Hive.isBoxOpen('indicator_cache')) {
await Hive.openBox<String>('indicator_cache');
}
if (kIsWeb) {
await Firebase.initializeApp(
options: const FirebaseOptions(
apiKey: 'AIzaSyB-QQbnq366f7rrKLVVFJKnSZgR9LcDWFI',
appId: '1:534109861443:android:25d267f0c986b2042f46f6',
messagingSenderId: '534109861443',
projectId: 'fcsc-uae-stats-9cb1e',
),
);
} else {
await Firebase.initializeApp();
}
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
);
FirebaseMessaging.onMessage.listen((RemoteMessage _) {});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage _) {});
// final themeProvider = ThemeProvider();
// themeProvider.setUserId("USER_ID_FROM_LOGIN"); // set this properly
// await themeProvider.loadThemeFromPocketBase();
//
// Request permission for notifications
// NotificationSettings settings = await messaging.requestPermission(
// alert: true,
// badge: true,
// sound: true,
// );
//
configureDependencies();
runApp(
const ProviderScope(
child: MainApp(),
),
);
}
// Future<void> triggerLocalNetworkAccess() async {
// try {
// await Socket.connect('127.0.0.1', 9).timeout(const Duration(seconds: 1));
// } catch (e) {
// // Ignore errors this is just to trigger the local network prompt
// }
// }
class MainApp extends ConsumerStatefulWidget {
const MainApp({super.key});
@override
ConsumerState<MainApp> createState() => _MainAppState();
}
class _MainAppState extends ConsumerState<MainApp> {
late AppsflyerSdk appsflyerSdk;
bool _listenerAttached = false;
String? pendingDeepLink;
Map<String, dynamic>? pendingDeepLinkData;
StreamSubscription<Uri>? _appLinkSubscription;
Timer? _oneLinkFallbackTimer;
@override
void initState() {
super.initState();
_initAppsFlyer();
_initUaePassDeepLinks();
}
@override
void dispose() {
_appLinkSubscription?.cancel();
_oneLinkFallbackTimer?.cancel();
super.dispose();
}
/// Handle deep links from AppsFlyer / custom URL schemes (no auth).
void _initUaePassDeepLinks() {
void handleIncomingLink(Uri? uri) {
if (uri == null) return;
// AppsFlyer OneLink fallback: fcscappstates://callback?deep_link_value=...
if (uri.scheme == 'fcscappstates') {
final deepLinkValue = uri.queryParameters['deep_link_value'];
if (deepLinkValue != null && deepLinkValue.isNotEmpty) {
_oneLinkFallbackTimer?.cancel();
final path =
deepLinkValue.startsWith('/') ? deepLinkValue : '/$deepLinkValue';
appRouter.go('/?intendedPath=${Uri.encodeComponent(path)}');
} else {
// Wait briefly for AppsFlyer SDK callback, then fallback to app root.
_oneLinkFallbackTimer?.cancel();
_oneLinkFallbackTimer = Timer(const Duration(milliseconds: 1500), () {
if (mounted) appRouter.go('/');
});
}
return;
}
// AppsFlyer OneLink universal link can arrive directly as https://fcscstats.onelink.me/...
// Handle it as a fallback in case SDK callback is delayed/missed in internal builds.
if (uri.scheme == 'https' && uri.host == 'fcscstats.onelink.me') {
final deepLinkValue = uri.queryParameters['deep_link_value'];
if (deepLinkValue != null && deepLinkValue.isNotEmpty) {
_oneLinkFallbackTimer?.cancel();
final path =
deepLinkValue.startsWith('/') ? deepLinkValue : '/$deepLinkValue';
appRouter.go('/?intendedPath=${Uri.encodeComponent(path)}');
} else {
// Some OneLink opens arrive without query params; AppsFlyer SDK usually
// delivers deep_link_value shortly after. Delay root fallback to avoid
// overriding the real deep-link route.
_oneLinkFallbackTimer?.cancel();
_oneLinkFallbackTimer = Timer(const Duration(milliseconds: 1500), () {
if (mounted) appRouter.go('/');
});
}
return;
}
}
WidgetsBinding.instance.addPostFrameCallback((_) async {
final appLinks = AppLinks();
final initial = await appLinks.getInitialLink();
handleIncomingLink(initial);
_appLinkSubscription = appLinks.uriLinkStream.listen((uri) {
handleIncomingLink(uri);
});
});
}
_initAppsFlyer() async {
// AppsFlyer dev key; OneLink template in share uses https://fcscstats.onelink.me/V6mR
// In AppsFlyer dashboard (OneLink template V6mR): set Android package ae.gov.fcsc.stats,
// iOS App ID (numeric from App Store Connect), and store fallback URLs to avoid app_not_found:
// Android: https://play.google.com/store/apps/details?id=ae.gov.fcsc.stats
// iOS: https://apps.apple.com/app/id1234567890 (replace with real App Store ID when live)
final options = Platform.isIOS
? AppsFlyerOptions(
afDevKey: 'K2unWqVxgJo84PDFVLiebC',
appId:
'6744919453', // Apple numeric App ID from App Store Connect (replace for production)
showDebug: true,
)
: AppsFlyerOptions(
afDevKey: 'K2unWqVxgJo84PDFVLiebC',
showDebug: true,
);
appsflyerSdk = AppsflyerSdk(options);
await appsflyerSdk.initSdk(
registerOnDeepLinkingCallback: true,
registerConversionDataCallback: true,
);
appsflyerSdk.startSDK();
// await appsflyerSdk.setDebugLog(true);
// Direct Deep Link (App already installed)
appsflyerSdk.onDeepLinking((DeepLinkResult result) {
if (result.status == Status.FOUND && result.deepLink != null) {
final deepLinkValue = result.deepLink!.deepLinkValue;
final data = result.deepLink!.clickEvent;
_handleDeepLink(deepLinkValue, data);
}
});
// Deferred Deep Link (First install case)
appsflyerSdk.onInstallConversionData((data) {
if (data['deep_link_value'] != null) {
_handleDeepLink(
data['deep_link_value'],
data,
);
}
});
}
void _handleDeepLink(String? deepLinkValue, Map<String, dynamic> data) {
_oneLinkFallbackTimer?.cancel();
String? resolved = deepLinkValue;
// AppsFlyer may not always populate deepLinkValue for complex links.
// Fallback to clickEvent payload keys.
resolved ??= data['deep_link_value']?.toString();
resolved ??= data['af_dp']?.toString();
resolved = resolved?.trim();
if (resolved == null || resolved.isEmpty) return;
// If AppsFlyer gives a full URL, preserve path+query only.
if (resolved.startsWith('http://') || resolved.startsWith('https://')) {
final parsed = Uri.tryParse(resolved);
if (parsed != null) {
resolved = parsed.path + (parsed.hasQuery ? '?${parsed.query}' : '');
}
}
// Decode once so encoded query paths like
// %2FpopulationGrowth%2Fgdp%3FbgColor%3D... become route-friendly.
try {
resolved = Uri.decodeComponent(resolved);
} catch (_) {}
// Use full path as intendedPath so shared links open the right screen.
final String intendedPath =
resolved!.startsWith('/') ? resolved : '/$resolved';
context.go('/?intendedPath=${Uri.encodeComponent(intendedPath)}');
}
@override
Widget build(BuildContext context) {
final locale = ref.watch(localeProvider);
// final connectivityProvider = Provider<bool>((ref) {
// return true; // Replace with actual logic
// });
//
// ref.listen<bool>(connectivityProvider, (previous, hasInternet) {
// print('Previous Internet Status: $previous');
// print('Current Internet Status: $hasInternet');
// if (previous != null && hasInternet != previous) {
// handleConnectivityChange(context, hasInternet);
// }
// });
/// ✅ SAFE PLACE FOR ref.listen
if (!_listenerAttached) {
_listenerAttached = true;
ref.listen<AsyncValue<bool>>(legacy_connectivity.connectivityProvider, (
previous,
next,
) {
final hasInternet = next.valueOrNull;
if (hasInternet == null) return;
handleConnectivityChange(hasInternet);
});
}
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: appRouter,
locale: locale,
theme: AppTheme.light,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
);
}
void handleConnectivityChange(bool hasInternet) {
final currentPath =
GoRouter.of(context).routerDelegate.currentConfiguration.fullPath;
if (!hasInternet && currentPath != AppRoutes.internetCheck) {
context.go(AppRoutes.internetCheck);
}
}
}