diff --git a/android/app/google-services.json b/android/app/google-services.json index e3232ac..4fe27f0 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -15,7 +15,7 @@ "oauth_client": [], "api_key": [ { - "current_key": "AIzaSyBJzL-jeo4xa_rQkHymzku_2lIJ6WJ8hoI" + "current_key": "AIzaSyBC4WTj3CtJOJL1QSxrxrmjUEp1SSKHKzs" } ], "services": { diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 8b1bd71..600941d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8625E4152F6E711300C953BC /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 86D2D4E2E379E6C7A77A04E3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -133,6 +134,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 8625E4152F6E711300C953BC /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -164,7 +166,6 @@ A43606B2521A3B50E340351F /* Pods-RunnerTests.release.xcconfig */, 97606C416DE8FC726A943531 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -493,17 +494,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = YK2DB9NT3S; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = BlindMaster; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.blindMaster; + PRODUCT_BUNDLE_IDENTIFIER = com.adipu.blindMaster; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -681,17 +685,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = YK2DB9NT3S; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = BlindMaster; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.blindMaster; + PRODUCT_BUNDLE_IDENTIFIER = com.adipu.blindMaster; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -707,17 +714,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = YK2DB9NT3S; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = BlindMaster; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.blindMaster; + PRODUCT_BUNDLE_IDENTIFIER = com.adipu.blindMaster; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index c30b367..0c024f1 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,4 +1,6 @@ import Flutter +import Firebase +import FirebaseMessaging import UIKit @main @@ -7,10 +9,24 @@ import UIKit _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + // Explicitly register for remote notifications on every launch so iOS always + // calls didRegisterForRemoteNotificationsWithDeviceToken, ensuring Firebase + // receives a fresh APNs token regardless of whether permission was already granted. + application.registerForRemoteNotifications() return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) } + + // FlutterImplicitEngineDelegate can interfere with Firebase's method swizzling, + // preventing it from capturing the APNs token. Forward it explicitly instead. + override func application( + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + ) { + Messaging.messaging().apnsToken = deviceToken + super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) + } } diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada4..7d10b4e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 7353c41..62a0144 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452..47cc06f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d93..d3401d7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cd7b00..1dba54b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe73094..4f106ff 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773c..8c63fec 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452..47cc06f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463..0feb517 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec3034..b17f2b8 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 0ec3034..b17f2b8 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea..d4e6921 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 84ac32a..2aefeaf 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba..049f2cf 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf1..2e4ae97 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist index 61c7616..91af243 100644 --- a/ios/Runner/GoogleService-Info.plist +++ b/ios/Runner/GoogleService-Info.plist @@ -3,13 +3,13 @@ API_KEY - AIzaSyC0MWniqI8flETaT8zKwXQzBhYRljKIKvk + AIzaSyAH5KvipSKH5J6dkjd6Ft7ALAqBYANB-Jo GCM_SENDER_ID 956683546941 PLIST_VERSION 1 BUNDLE_ID - com.example.blindMaster + com.adipu.blindMaster PROJECT_ID blindmaster-54055 STORAGE_BUCKET @@ -25,6 +25,6 @@ IS_SIGNIN_ENABLED GOOGLE_APP_ID - 1:956683546941:ios:162a6b8aa58f1eb8121554 + 1:956683546941:ios:1059be0ae683894b121554 \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index eeeaa15..1d4dbcf 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -32,7 +32,7 @@ NSBluetoothAlwaysUsageDescription - This app uses Bluetooth to connect to nearby devices. + This app uses Bluetooth to provision BlindMaster devices for the first time. NSBluetoothPeripheralUsageDescription This app requires Bluetooth access. NSBonjourServices @@ -76,15 +76,13 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index 0c67376..28c29bf 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -1,5 +1,8 @@ - + + aps-environment + production + diff --git a/lib/BlindMasterResources/fcm_service.dart b/lib/BlindMasterResources/fcm_service.dart index 77170a8..0b91db7 100644 --- a/lib/BlindMasterResources/fcm_service.dart +++ b/lib/BlindMasterResources/fcm_service.dart @@ -1,18 +1,50 @@ import 'package:blind_master/BlindMasterResources/secure_transmissions.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; class FcmService { /// Request permission, fetch the FCM token, and register it with the server. /// Safe to call on every login/session-restore — the server just upserts the value. static Future register() async { + debugPrint('FCM: register() called'); try { final messaging = FirebaseMessaging.instance; - await messaging.requestPermission(alert: true, badge: true, sound: true); + final settings = await messaging.requestPermission(alert: true, badge: true, sound: true); + debugPrint('FCM: authorization status: ${settings.authorizationStatus}'); + if (settings.authorizationStatus == AuthorizationStatus.denied) { + debugPrint('FCM: notifications denied — enable in Settings > [App] > Notifications'); + return; + } + await messaging.setForegroundNotificationPresentationOptions( + alert: true, + badge: true, + sound: true, + ); + // On iOS, APNs token must be available before FCM token can be fetched. + // getAPNSToken() can block if iOS hasn't finished APNs registration yet, + // so cap it with a timeout and retry once after a short delay. + if (defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS) { + String? apnsToken = await messaging.getAPNSToken() + .timeout(const Duration(seconds: 3), onTimeout: () => null); + if (apnsToken == null) { + debugPrint('FCM: APNs token not ready, retrying in 5s...'); + await Future.delayed(const Duration(seconds: 5)); + apnsToken = await messaging.getAPNSToken() + .timeout(const Duration(seconds: 5), onTimeout: () => null); + } + if (apnsToken == null) { + debugPrint('FCM: APNs token unavailable — simulator or APNs not configured'); + return; + } + debugPrint('FCM: APNs token acquired'); + } final token = await messaging.getToken(); + debugPrint('FCM TOKEN: ${token ?? "null — likely running on simulator"}'); if (token == null) return; await securePost({'token': token}, 'register_fcm_token'); - } catch (_) { - // Non-fatal — push notifications simply won't work until the next successful registration. + } catch (e) { + debugPrint('FCM registration failed: $e'); } } } diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index 7c60b25..bf90eb9 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -59,21 +59,21 @@ class DefaultFirebaseOptions { ); static const FirebaseOptions ios = FirebaseOptions( - apiKey: 'AIzaSyC0MWniqI8flETaT8zKwXQzBhYRljKIKvk', - appId: '1:956683546941:ios:162a6b8aa58f1eb8121554', + apiKey: 'AIzaSyAH5KvipSKH5J6dkjd6Ft7ALAqBYANB-Jo', + appId: '1:956683546941:ios:1059be0ae683894b121554', messagingSenderId: '956683546941', projectId: 'blindmaster-54055', storageBucket: 'blindmaster-54055.firebasestorage.app', - iosBundleId: 'com.example.blindMaster', + iosBundleId: 'com.adipu.blindMaster', ); static const FirebaseOptions macos = FirebaseOptions( - apiKey: 'AIzaSyC0MWniqI8flETaT8zKwXQzBhYRljKIKvk', - appId: '1:956683546941:ios:162a6b8aa58f1eb8121554', + apiKey: 'AIzaSyAH5KvipSKH5J6dkjd6Ft7ALAqBYANB-Jo', + appId: '1:956683546941:ios:1059be0ae683894b121554', messagingSenderId: '956683546941', projectId: 'blindmaster-54055', storageBucket: 'blindmaster-54055.firebasestorage.app', - iosBundleId: 'com.example.blindMaster', + iosBundleId: 'com.adipu.blindMaster', ); static const FirebaseOptions windows = FirebaseOptions( diff --git a/lib/main.dart b/lib/main.dart index a3c0af7..6e93358 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:blind_master/BlindMasterResources/fcm_service.dart'; import 'package:blind_master/BlindMasterScreens/Startup/splash_screen.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; @@ -16,6 +17,7 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + FirebaseMessaging.instance.onTokenRefresh.listen((_) => FcmService.register()); runApp(const MyApp()); }