From ef5609ba57efe81bd25d6cf94f245ce5082f16a9 Mon Sep 17 00:00:00 2001 From: John Mizerek Date: Thu, 8 Jan 2026 15:05:11 -0800 Subject: [PATCH] Fix iOS beacon permissions for location and Bluetooth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iOS Permissions: - Add missing Info.plist keys for location and Bluetooth usage descriptions - Configure Podfile to enable PERMISSION_LOCATION and PERMISSION_BLUETOOTH - Fix beacon_permissions.dart to use correct iOS permission (Permission.bluetooth) instead of Android-only bluetoothScan/bluetoothConnect The permission_handler plugin requires platform-specific handling: - iOS: Uses Permission.bluetooth for Bluetooth access - Android: Uses Permission.bluetoothScan and Permission.bluetoothConnect 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- ios/Podfile | 9 ++++ ios/Podfile.lock | 2 +- ios/Runner.xcodeproj/project.pbxproj | 4 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- ios/Runner/Info.plist | 8 ++++ lib/services/beacon_permissions.dart | 44 ++++++++++++------- 6 files changed, 51 insertions(+), 18 deletions(-) diff --git a/ios/Podfile b/ios/Podfile index 620e46e..67a0e60 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -39,5 +39,14 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + + # Enable permissions for permission_handler + target.build_configurations.each do |config| + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + 'PERMISSION_LOCATION=1', + 'PERMISSION_BLUETOOTH=1', + ] + end end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6d3f2eb..cdf3a87 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -108,6 +108,6 @@ SPEC CHECKSUMS: StripePaymentsUI: 326376e23caa369d1f58041bdb858c89c2b17ed4 StripeUICore: 17a4f3adb81ae05ab885e1b353022a430176eab1 -PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e +PODFILE CHECKSUM: 585f1361913628fd422da91acb10f1ea62e8189d COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 3ac7715..1119a3b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -97,7 +97,6 @@ F2C711915F4DC721B46B55FB /* Pods-RunnerTests.release.xcconfig */, BF07229F38A392371BF2BB5F /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -490,6 +489,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2UD2H98KPK; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -674,6 +674,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2UD2H98KPK; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -696,6 +697,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2UD2H98KPK; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d4..fa4cdb6 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -52,7 +52,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSLocationWhenInUseUsageDescription + Payfrit needs your location to find nearby restaurant tables and beacons. + NSLocationAlwaysAndWhenInUseUsageDescription + Payfrit needs your location to find nearby restaurant tables and beacons. + NSBluetoothAlwaysUsageDescription + Payfrit uses Bluetooth to detect table beacons for seamless ordering. + NSBluetoothPeripheralUsageDescription + Payfrit uses Bluetooth to detect table beacons for seamless ordering. CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents diff --git a/lib/services/beacon_permissions.dart b/lib/services/beacon_permissions.dart index 59a192b..cbd4746 100644 --- a/lib/services/beacon_permissions.dart +++ b/lib/services/beacon_permissions.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -6,22 +7,29 @@ class BeaconPermissions { try { // Request location permission (required for Bluetooth scanning) final locationStatus = await Permission.locationWhenInUse.request(); + debugPrint('[BeaconPermissions] Location: $locationStatus'); - // Request Bluetooth permissions (Android 12+) - final bluetoothScan = await Permission.bluetoothScan.request(); - final bluetoothConnect = await Permission.bluetoothConnect.request(); + bool bluetoothGranted = true; - final allGranted = locationStatus.isGranted && - bluetoothScan.isGranted && - bluetoothConnect.isGranted; + if (Platform.isIOS) { + // iOS uses a single Bluetooth permission + final bluetoothStatus = await Permission.bluetooth.request(); + debugPrint('[BeaconPermissions] Bluetooth (iOS): $bluetoothStatus'); + bluetoothGranted = bluetoothStatus.isGranted; + } else { + // Android 12+ requires separate scan/connect permissions + final bluetoothScan = await Permission.bluetoothScan.request(); + final bluetoothConnect = await Permission.bluetoothConnect.request(); + debugPrint('[BeaconPermissions] BluetoothScan: $bluetoothScan, BluetoothConnect: $bluetoothConnect'); + bluetoothGranted = bluetoothScan.isGranted && bluetoothConnect.isGranted; + } + + final allGranted = locationStatus.isGranted && bluetoothGranted; if (allGranted) { debugPrint('[BeaconPermissions] ✅ All permissions granted'); } else { - debugPrint('[BeaconPermissions] ❌ Permissions denied: ' - 'location=$locationStatus, ' - 'bluetoothScan=$bluetoothScan, ' - 'bluetoothConnect=$bluetoothConnect'); + debugPrint('[BeaconPermissions] ❌ Permissions denied'); } return allGranted; @@ -33,12 +41,18 @@ class BeaconPermissions { static Future checkPermissions() async { final locationStatus = await Permission.locationWhenInUse.status; - final bluetoothScan = await Permission.bluetoothScan.status; - final bluetoothConnect = await Permission.bluetoothConnect.status; - return locationStatus.isGranted && - bluetoothScan.isGranted && - bluetoothConnect.isGranted; + bool bluetoothGranted = true; + if (Platform.isIOS) { + final bluetoothStatus = await Permission.bluetooth.status; + bluetoothGranted = bluetoothStatus.isGranted; + } else { + final bluetoothScan = await Permission.bluetoothScan.status; + final bluetoothConnect = await Permission.bluetoothConnect.status; + bluetoothGranted = bluetoothScan.isGranted && bluetoothConnect.isGranted; + } + + return locationStatus.isGranted && bluetoothGranted; } static Future openSettings() async {