diff --git a/README.md b/README.md index a1e015c61..f5637e6ac 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,4 @@ The Flutter permission_handler plugin is build following the federated plugin ar [1]: https://pub.dev/packages/permission_handler [2]: ./permission_handler/README.md [3]: https://pub.dev/packages/permission_handler_platform_interface -[4]: ./permission_handler_platform_interface/README.md +[4]: ./permission_handler_platform_interface/README.md \ No newline at end of file diff --git a/permission_handler/CHANGELOG.md b/permission_handler/CHANGELOG.md index 65b8ef989..636c563b9 100644 --- a/permission_handler/CHANGELOG.md +++ b/permission_handler/CHANGELOG.md @@ -1,3 +1,7 @@ +## 11.4.1 + +* Add `getLocationAccuracy` and `requestTemporaryFullAccuracy` methods. Implementation is done only for iOS. + ## 11.3.1 * Documents the use of the `PERMISSION_LOCAITON_WHENINUSE` macro on iOS. diff --git a/permission_handler/README.md b/permission_handler/README.md index 0ef5377b5..375766fd0 100644 --- a/permission_handler/README.md +++ b/permission_handler/README.md @@ -147,23 +147,23 @@ You must list the permission you want to use in your application: e.g. when you don't need camera permission, just delete 'NSCameraUsageDescription' The following lists the relationship between `Permission` and `The key of Info.plist`: -| Permission | Info.plist | Macro | -|---------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| PermissionGroup.calendar (< iOS 17) | NSCalendarsUsageDescription | PERMISSION_EVENTS | -| PermissionGroup.calendarWriteOnly (iOS 17+) | NSCalendarsWriteOnlyAccessUsageDescription | PERMISSION_EVENTS | -| PermissionGroup.calendarFullAccess (iOS 17+) | NSCalendarsFullAccessUsageDescription | PERMISSION_EVENTS_FULL_ACCESS | -| PermissionGroup.reminders | NSRemindersUsageDescription | PERMISSION_REMINDERS | -| PermissionGroup.contacts | NSContactsUsageDescription | PERMISSION_CONTACTS | -| PermissionGroup.camera | NSCameraUsageDescription | PERMISSION_CAMERA | -| PermissionGroup.microphone | NSMicrophoneUsageDescription | PERMISSION_MICROPHONE | -| PermissionGroup.speech | NSSpeechRecognitionUsageDescription | PERMISSION_SPEECH_RECOGNIZER | -| PermissionGroup.photos | NSPhotoLibraryUsageDescription | PERMISSION_PHOTOS | -| PermissionGroup.photosAddOnly | NSPhotoLibraryAddUsageDescription | PERMISSION_PHOTOS_ADD_ONLY | -| PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse | NSLocationUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationWhenInUseUsageDescription | PERMISSION_LOCATION | -| PermissionGroup.locationWhenInUse | NSLocationWhenInUseUsageDescription | PERMISSION_LOCATION_WHENINUSE | -| PermissionGroup.notification | PermissionGroupNotification | PERMISSION_NOTIFICATIONS | +| Permission | Info.plist | Macro | +| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| PermissionGroup.calendar (< iOS 17) | NSCalendarsUsageDescription | PERMISSION_EVENTS | +| PermissionGroup.calendarWriteOnly (iOS 17+) | NSCalendarsWriteOnlyAccessUsageDescription | PERMISSION_EVENTS | +| PermissionGroup.calendarFullAccess (iOS 17+) | NSCalendarsFullAccessUsageDescription | PERMISSION_EVENTS_FULL_ACCESS | +| PermissionGroup.reminders | NSRemindersUsageDescription | PERMISSION_REMINDERS | +| PermissionGroup.contacts | NSContactsUsageDescription | PERMISSION_CONTACTS | +| PermissionGroup.camera | NSCameraUsageDescription | PERMISSION_CAMERA | +| PermissionGroup.microphone | NSMicrophoneUsageDescription | PERMISSION_MICROPHONE | +| PermissionGroup.speech | NSSpeechRecognitionUsageDescription | PERMISSION_SPEECH_RECOGNIZER | +| PermissionGroup.photos | NSPhotoLibraryUsageDescription | PERMISSION_PHOTOS | +| PermissionGroup.photosAddOnly | NSPhotoLibraryAddUsageDescription | PERMISSION_PHOTOS_ADD_ONLY | +| PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse | NSLocationUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationWhenInUseUsageDescription | PERMISSION_LOCATION | +| PermissionGroup.locationWhenInUse | NSLocationWhenInUseUsageDescription | PERMISSION_LOCATION_WHENINUSE | +| PermissionGroup.notification | PermissionGroupNotification | PERMISSION_NOTIFICATIONS | | PermissionGroup.mediaLibrary | NSAppleMusicUsageDescription, kTCCServiceMedia | -PERMISSION_MEDIA_LIBRARY | +| PERMISSION_MEDIA_LIBRARY | 4. Clean & Rebuild diff --git a/permission_handler/example/ios/Podfile b/permission_handler/example/ios/Podfile index 008edb50d..56838fa0b 100644 --- a/permission_handler/example/ios/Podfile +++ b/permission_handler/example/ios/Podfile @@ -47,7 +47,7 @@ post_install do |installer| ## dart: [PermissionGroup.calendarWriteOnly, PermissionGroup.calendar (until iOS 16)] 'PERMISSION_EVENTS=1', - + ## dart: [PermissionGroup.calendarFullAccess, PermissionGroup.calendar (from iOS 17)] 'PERMISSION_EVENTS_FULL_ACCESS=1', diff --git a/permission_handler/example/ios/Runner.xcodeproj/project.pbxproj b/permission_handler/example/ios/Runner.xcodeproj/project.pbxproj index 5b918732b..56eed4076 100644 --- a/permission_handler/example/ios/Runner.xcodeproj/project.pbxproj +++ b/permission_handler/example/ios/Runner.xcodeproj/project.pbxproj @@ -141,6 +141,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, D38B08CB85942E5D11545EE3 /* [CP] Embed Pods Frameworks */, + 2E66F0981C0A9CA5865C1D97 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -157,7 +158,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -199,6 +200,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2E66F0981C0A9CA5865C1D97 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -344,7 +362,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -430,7 +448,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -479,7 +497,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/permission_handler/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/permission_handler/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a6b826db2..5e31d3d34 100644 --- a/permission_handler/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/permission_handler/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + NSLocationTemporaryUsageDescriptionDictionary + + YourPurposeKey + The example App requires temporary access to the device&apos;s precise location. + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -41,74 +46,47 @@ UIViewControllerBasedStatusBarAppearance - - - NSLocationWhenInUseUsageDescription - Need location when in use - NSLocationAlwaysAndWhenInUseUsageDescription - Always and when in use! - NSLocationUsageDescription - Older devices need location. - NSLocationAlwaysUsageDescription - Can I have location always? - - - NSAppleMusicUsageDescription - Music! - kTCCServiceMediaLibrary - media - - - NSCalendarsUsageDescription - Calendars - NSCalendarsFullAccessUsageDescription - Calendar full access - - - NSCameraUsageDescription - camera - - - NSContactsUsageDescription - contacts - - - NSMicrophoneUsageDescription - microphone - - - NSSpeechRecognitionUsageDescription - speech - - - NSMotionUsageDescription - motion - - - NSPhotoLibraryUsageDescription - photos - - - NSRemindersUsageDescription - reminders - - - NSBluetoothAlwaysUsageDescription - bluetooth - NSBluetoothPeripheralUsageDescription - bluetooth - - - NSUserTrackingUsageDescription - appTrackingTransparency - - - NSSiriUsageDescription - The example app would like access to Siri Kit to demonstrate requesting authorization. - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - + NSLocationWhenInUseUsageDescription + Need location when in use + NSLocationAlwaysAndWhenInUseUsageDescription + Always and when in use! + NSLocationUsageDescription + Older devices need location. + NSLocationAlwaysUsageDescription + Can I have location always? + NSAppleMusicUsageDescription + Music! + kTCCServiceMediaLibrary + media + NSCalendarsUsageDescription + Calendars + NSCalendarsFullAccessUsageDescription + Calendar full access + NSCameraUsageDescription + camera + NSContactsUsageDescription + contacts + NSMicrophoneUsageDescription + microphone + NSSpeechRecognitionUsageDescription + speech + NSMotionUsageDescription + motion + NSPhotoLibraryUsageDescription + photos + NSRemindersUsageDescription + reminders + NSBluetoothAlwaysUsageDescription + bluetooth + NSBluetoothPeripheralUsageDescription + bluetooth + NSUserTrackingUsageDescription + appTrackingTransparency + NSSiriUsageDescription + The example app would like access to Siri Kit to demonstrate requesting authorization. + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/permission_handler_apple/example/lib/main.dart b/permission_handler_apple/example/lib/main.dart index f35cf7592..fa26f5e9f 100644 --- a/permission_handler_apple/example/lib/main.dart +++ b/permission_handler_apple/example/lib/main.dart @@ -104,6 +104,14 @@ class _PermissionState extends State { } } + Widget getCheckPermissionServiceStatus() { + return _InfoButton( + onPressed: () { + checkServiceStatus(context, _permission as PermissionWithService); + }, + ); + } + @override Widget build(BuildContext context) { return ListTile( @@ -116,15 +124,18 @@ class _PermissionState extends State { style: TextStyle(color: getPermissionColor()), ), trailing: (_permission is PermissionWithService) - ? IconButton( - icon: const Icon( - Icons.info, - color: Colors.white, - ), - onPressed: () { - checkServiceStatus( - context, _permission as PermissionWithService); - }) + ? _permission == Permission.location + ? Wrap( + children: [ + _InfoButton(onPressed: () { + checkLocationAccuracy( + context, + ); + }), + getCheckPermissionServiceStatus() + ], + ) + : getCheckPermissionServiceStatus() : null, onTap: () { requestPermission(_permission); @@ -140,6 +151,27 @@ class _PermissionState extends State { )); } + void checkLocationAccuracy(BuildContext context) async { + final locationAccuracyStatus = + await _permissionHandler.getLocationAccuracy(); + _showLocationAccuracySnackBar(context, locationAccuracyStatus); + } + + void _showLocationAccuracySnackBar( + BuildContext context, LocationAccuracyStatus locationAccuracyStatus) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(locationAccuracyStatus.toString()), + action: SnackBarAction( + label: 'Request', + onPressed: () async { + final locationAccuracyStatus = await _permissionHandler + .requestTemporaryFullAccuracy('YourPurposeKey'); + _showLocationAccuracySnackBar(context, locationAccuracyStatus); + }, + ), + )); + } + Future requestPermission(Permission permission) async { final status = await _permissionHandler.requestPermissions([permission]); @@ -150,3 +182,23 @@ class _PermissionState extends State { }); } } + +class _InfoButton extends StatelessWidget { + const _InfoButton({ + Key? key, + required this.onPressed, + }) : super(key: key); + + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + return IconButton( + icon: const Icon( + Icons.info, + color: Colors.white, + ), + onPressed: onPressed, + ); + } +} diff --git a/permission_handler_apple/ios/Classes/LocationAccuracyHandler.h b/permission_handler_apple/ios/Classes/LocationAccuracyHandler.h new file mode 100644 index 000000000..08a241321 --- /dev/null +++ b/permission_handler_apple/ios/Classes/LocationAccuracyHandler.h @@ -0,0 +1,25 @@ +// +// LocationAccuracyHandler.h +// Pods +// +// Created by Pierre Monier on 09/02/2025. +// + +#ifndef LocationAccuracyHandler_h +#define LocationAccuracyHandler_h +#import "PermissionHandlerEnums.h" + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +@interface LocationAccuracyHandler : NSObject + +- (LocationAccuracy) getLocationAccuracy; +- (void) requestTemporaryFullAccuracyWithResult:(FlutterResult _Nonnull)result purposeKey:(NSString * _Nullable)purposeKey; + +@end + +#endif /* LocationAccuracyHandler_h */ diff --git a/permission_handler_apple/ios/Classes/LocationAccuracyHandler.m b/permission_handler_apple/ios/Classes/LocationAccuracyHandler.m new file mode 100644 index 000000000..68c179603 --- /dev/null +++ b/permission_handler_apple/ios/Classes/LocationAccuracyHandler.m @@ -0,0 +1,78 @@ +// +// LocationAccuracyHandler.m +// permission_handler_apple +// +// Created by Pierre Monier on 09/02/2025. +// + +#import +#import +#import "LocationAccuracyHandler.h" +#import "PermissionHandlerEnums.h" +#import "util/Codec.h" + +@interface LocationAccuracyHandler() +@property (strong, nonatomic) CLLocationManager *locationManager; +@end + +@implementation LocationAccuracyHandler + +- (id) init { + self = [super init]; + + if (!self) { + return nil; + } + + self.locationManager = [[CLLocationManager alloc] init]; + return self; +} + +- (LocationAccuracy) getLocationAccuracy { +#if TARGET_OS_OSX + return LocationAccuracyPrecise; +#else + if (@available(iOS 14, macOS 10.16, *)) { + switch (_locationManager.accuracyAuthorization) { + case CLAccuracyAuthorizationFullAccuracy: + return LocationAccuracyPrecise; + case CLAccuracyAuthorizationReducedAccuracy: + return LocationAccuracyReduced; + default: + // Reduced location accuracy is the default on iOS 14+ and macOS 11+. + return LocationAccuracyReduced; + } + } else { + // Approximate location is not available, return precise location. + return LocationAccuracyPrecise; + } +#endif +} + +- (void)requestTemporaryFullAccuracyWithResult:(FlutterResult)result purposeKey:(NSString * _Nullable)purposeKey { + if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationTemporaryUsageDescriptionDictionary"] == nil) { + result([NSException exceptionWithName:@"MISSING_USAGE_DESCRIPTION" + reason:@"The temporary accuracy dictionary key is not set in the Info.plist" + userInfo:nil]); + } + + #if TARGET_OS_OSX + return result([Codec encodeLocationAccuracy:LocationAccuracyPrecise]); + #else + + if (@available(iOS 14.0, macOS 10.16, *)) { + [_locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:purposeKey + completion:^(NSError *_Nullable error) { + if ([self->_locationManager accuracyAuthorization] == CLAccuracyAuthorizationFullAccuracy) { + return result([Codec encodeLocationAccuracy:LocationAccuracyPrecise]); + } else { + return result([Codec encodeLocationAccuracy:LocationAccuracyReduced]); + } + }]; + } else { + return result([Codec encodeLocationAccuracy:LocationAccuracyPrecise]); + } + #endif +} + +@end diff --git a/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h b/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h index c153dc500..10ec596f5 100644 --- a/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h +++ b/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h @@ -180,3 +180,9 @@ typedef NS_ENUM(int, ServiceStatus) { ServiceStatusEnabled, ServiceStatusNotApplicable, }; + +typedef NS_ENUM(int, LocationAccuracy) { + LocationAccuracyReduced = 0, + LocationAccuracyPrecise = 1, + LocationAccuracyUnknown = 2 +}; diff --git a/permission_handler_apple/ios/Classes/PermissionHandlerPlugin.m b/permission_handler_apple/ios/Classes/PermissionHandlerPlugin.m index cfbad93cb..f2f93d00a 100644 --- a/permission_handler_apple/ios/Classes/PermissionHandlerPlugin.m +++ b/permission_handler_apple/ios/Classes/PermissionHandlerPlugin.m @@ -1,8 +1,9 @@ #import "PermissionHandlerPlugin.h" - +#import "LocationAccuracyHandler.h" @implementation PermissionHandlerPlugin { PermissionManager *_Nonnull _permissionManager; + LocationAccuracyHandler *_Nonnull _locationAccuracyHandler; _Nullable FlutterResult _methodResult; } @@ -12,6 +13,7 @@ - (instancetype)initWithPermissionManager:(PermissionManager *)permissionManager _permissionManager = permissionManager; } + _locationAccuracyHandler = [[LocationAccuracyHandler alloc] init]; return self; } @@ -57,6 +59,13 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result result(@false); } else if ([@"openAppSettings" isEqualToString:call.method]) { [PermissionManager openAppSettings:result]; + } else if ([@"getLocationAccuracy" isEqualToString:call.method]) { + LocationAccuracy locationAccuracy = [self->_locationAccuracyHandler getLocationAccuracy]; + + result([Codec encodeLocationAccuracy:locationAccuracy]); + } else if ([@"requestTemporaryFullAccuracy" isEqualToString:call.method]) { + NSString* purposeKey = (NSString *)call.arguments[@"purposeKey"]; + [self->_locationAccuracyHandler requestTemporaryFullAccuracyWithResult:result purposeKey:purposeKey]; } else { result(FlutterMethodNotImplemented); } diff --git a/permission_handler_apple/ios/Classes/strategies/LocationPermissionStrategy.m b/permission_handler_apple/ios/Classes/strategies/LocationPermissionStrategy.m index a4f6b21f4..d98f29ebe 100644 --- a/permission_handler_apple/ios/Classes/strategies/LocationPermissionStrategy.m +++ b/permission_handler_apple/ios/Classes/strategies/LocationPermissionStrategy.m @@ -229,4 +229,4 @@ + (PermissionStatus)determinePermissionStatus:(PermissionGroup)permission author @implementation LocationPermissionStrategy @end -#endif +#endif \ No newline at end of file diff --git a/permission_handler_apple/ios/Classes/util/Codec.h b/permission_handler_apple/ios/Classes/util/Codec.h index ad930a490..49dc5067c 100644 --- a/permission_handler_apple/ios/Classes/util/Codec.h +++ b/permission_handler_apple/ios/Classes/util/Codec.h @@ -14,4 +14,6 @@ + (NSNumber *_Nullable)encodePermissionStatus:(enum PermissionStatus)permissionStatus; + (NSNumber *_Nullable)encodeServiceStatus:(enum ServiceStatus)serviceStatus; + ++ (NSNumber *_Nullable)encodeLocationAccuracy:(enum LocationAccuracy)locationAccuracy; @end diff --git a/permission_handler_apple/ios/Classes/util/Codec.m b/permission_handler_apple/ios/Classes/util/Codec.m index a7447fe01..f469fc75d 100644 --- a/permission_handler_apple/ios/Classes/util/Codec.m +++ b/permission_handler_apple/ios/Classes/util/Codec.m @@ -26,4 +26,8 @@ + (NSNumber *_Nullable)encodeServiceStatus:(enum ServiceStatus)serviceStatus { return [[NSNumber alloc] initWithInt:serviceStatus]; } ++ (NSNumber *_Nullable)encodeLocationAccuracy:(enum LocationAccuracy)locationAccuracy { + return [[NSNumber alloc] initWithInt:locationAccuracy]; +} + @end diff --git a/permission_handler_apple/pubspec.yaml b/permission_handler_apple/pubspec.yaml index ef6679c56..6024f3a38 100644 --- a/permission_handler_apple/pubspec.yaml +++ b/permission_handler_apple/pubspec.yaml @@ -2,7 +2,7 @@ name: permission_handler_apple description: Permission plugin for Flutter. This plugin provides the iOS API to request and check permissions. repository: https://github.com/baseflow/flutter-permission-handler issue_tracker: https://github.com/Baseflow/flutter-permission-handler/issues -version: 9.4.5 +version: 9.5.5 environment: sdk: ">=2.15.0 <4.0.0" diff --git a/permission_handler_platform_interface/CHANGELOG.md b/permission_handler_platform_interface/CHANGELOG.md index 6e3b35cc9..343b38bb8 100644 --- a/permission_handler_platform_interface/CHANGELOG.md +++ b/permission_handler_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.3.3 + +* Add `getLocationAccuracy` and `requestTemporaryFullAccuracy` methods and `LocationAccuracyStatus` enum. + ## 4.2.3 * Fixes class name references in the API documentation. diff --git a/permission_handler_platform_interface/lib/permission_handler_platform_interface.dart b/permission_handler_platform_interface/lib/permission_handler_platform_interface.dart index 4f44f6143..e9b19bade 100644 --- a/permission_handler_platform_interface/lib/permission_handler_platform_interface.dart +++ b/permission_handler_platform_interface/lib/permission_handler_platform_interface.dart @@ -9,3 +9,4 @@ part 'src/permission_handler_platform_interface.dart'; part 'src/permission_status.dart'; part 'src/permissions.dart'; part 'src/service_status.dart'; +part 'src/location_accuracy_status.dart'; diff --git a/permission_handler_platform_interface/lib/src/location_accuracy_status.dart b/permission_handler_platform_interface/lib/src/location_accuracy_status.dart new file mode 100644 index 000000000..c168cb5b3 --- /dev/null +++ b/permission_handler_platform_interface/lib/src/location_accuracy_status.dart @@ -0,0 +1,65 @@ +part of permission_handler_platform_interface; + +/// Represent the current Location Accuracy Status on iOS 14.0 and higher and +/// Android 12 and higher. +enum LocationAccuracyStatus { + /// An approximate location will be returned. + reduced, + + /// A precise location will be returned. + precise, + + /// We can't determine the location accuracy status. + unknown, +} + +/// Conversion extension methods for the [LocationAccuracyStatus] type. +extension LocationAccuracyStatusValue on LocationAccuracyStatus { + /// Converts the [LocationAccuracyStatus] value into an integer. + int get value { + switch (this) { + case LocationAccuracyStatus.reduced: + return 0; + case LocationAccuracyStatus.precise: + return 1; + case LocationAccuracyStatus.unknown: + return 2; + default: + throw UnimplementedError(); + } + } + + /// Converts the supplied integer value into a [LocationAccuracyStatus] enum. + static LocationAccuracyStatus statusByValue(int value) { + return [ + LocationAccuracyStatus.reduced, + LocationAccuracyStatus.precise, + LocationAccuracyStatus.unknown, + ][value]; + } +} + +/// Utility getter extensions for the [LocationAccuracyStatus] type. +extension LocationAccuracyStatusGetters on LocationAccuracyStatus { + /// If the location accuracy is reduced. + bool get isReduced => this == LocationAccuracyStatus.reduced; + + /// If the location accuracy is precise. + bool get isPrecise => this == LocationAccuracyStatus.precise; + + /// If the location accuracy status is unknown. + bool get isUnknown => this == LocationAccuracyStatus.unknown; +} + +/// Utility getter extensions for the `Future` type. +extension FutureLocationAccuracyStatusGetters + on Future { + /// If the location accuracy is reduced. + Future get isReduced async => (await this).isReduced; + + /// If the location accuracy is precise. + Future get isPrecise async => (await this).isPrecise; + + /// If the location accuracy status is unknown. + Future get isUnknown async => (await this).isUnknown; +} diff --git a/permission_handler_platform_interface/lib/src/method_channel/method_channel_permission_handler.dart b/permission_handler_platform_interface/lib/src/method_channel/method_channel_permission_handler.dart index 5f03e3e7a..eb505ec79 100644 --- a/permission_handler_platform_interface/lib/src/method_channel/method_channel_permission_handler.dart +++ b/permission_handler_platform_interface/lib/src/method_channel/method_channel_permission_handler.dart @@ -98,4 +98,33 @@ class MethodChannelPermissionHandler extends PermissionHandlerPlatform { return shouldShowRationale ?? false; } + + @override + Future getLocationAccuracy() async { + if (defaultTargetPlatform != TargetPlatform.iOS) { + return LocationAccuracyStatus.unknown; + } + + final locationAccuracyStatus = + await _methodChannel.invokeMethod('getLocationAccuracy'); + + return decodeLocationAccuracyStatus(locationAccuracyStatus); + } + + @override + Future requestTemporaryFullAccuracy( + String purposeKey) async { + if (defaultTargetPlatform != TargetPlatform.iOS) { + return LocationAccuracyStatus.unknown; + } + + final locationAccuracyStatus = await _methodChannel.invokeMethod( + 'requestTemporaryFullAccuracy', + { + 'purposeKey': purposeKey, + }, + ); + + return decodeLocationAccuracyStatus(locationAccuracyStatus); + } } diff --git a/permission_handler_platform_interface/lib/src/method_channel/utils/codec.dart b/permission_handler_platform_interface/lib/src/method_channel/utils/codec.dart index dc5db075c..877205510 100644 --- a/permission_handler_platform_interface/lib/src/method_channel/utils/codec.dart +++ b/permission_handler_platform_interface/lib/src/method_channel/utils/codec.dart @@ -18,6 +18,11 @@ Map decodePermissionRequestResult( Permission.byValue(key), PermissionStatusValue.statusByValue(value))); } +/// Convert the given [value] into a [LocationAccuracyStatus] instance. +LocationAccuracyStatus decodeLocationAccuracyStatus(int value) { + return LocationAccuracyStatusValue.statusByValue(value); +} + /// Converts the given [List] of [Permission]s into a [List] of [int]s which /// can be sent on the Flutter method channel. List encodePermissions(List permissions) { diff --git a/permission_handler_platform_interface/lib/src/permission_handler_platform_interface.dart b/permission_handler_platform_interface/lib/src/permission_handler_platform_interface.dart index 65d965bf6..e99b75983 100644 --- a/permission_handler_platform_interface/lib/src/permission_handler_platform_interface.dart +++ b/permission_handler_platform_interface/lib/src/permission_handler_platform_interface.dart @@ -85,4 +85,29 @@ abstract class PermissionHandlerPlatform extends PlatformInterface { throw UnimplementedError( 'shouldShowRequestPermissionRationale() has not been implemented.'); } + + /// Get the location accuracy given to your app. + /// + /// Currently only supported on iOS 14.0 and higher. + Future getLocationAccuracy() { + throw UnimplementedError('getLocationAccuracy() has not been implemented.'); + } + + /// Requests temporary precise location when the user only gave permission + /// for approximate location (iOS 14+ only) + /// + /// When using this method, the value of the required property `purposeKey` + /// should match the value given in the + /// `NSLocationTemporaryUsageDescription` dictionary in the + /// Info.plist. + /// + /// Throws a [PermissionDefinitionsNotFoundException] when the necessary key + /// in the Info.plist is not added + /// Returns [LocationAccuracyStatus.precise] when using iOS 13 or below or + /// using other platforms. + Future requestTemporaryFullAccuracy( + String purposeKey) { + throw UnimplementedError( + 'requestTemporaryFullAccuracy() has not been implemented.'); + } } diff --git a/permission_handler_platform_interface/pubspec.yaml b/permission_handler_platform_interface/pubspec.yaml index ca3aea932..9f3e3b55c 100644 --- a/permission_handler_platform_interface/pubspec.yaml +++ b/permission_handler_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the permission_handler plugin. homepage: https://github.com/baseflow/flutter-permission-handler/tree/master/permission_handler_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 4.2.3 +version: 4.3.3 dependencies: flutter: diff --git a/permission_handler_platform_interface/test/src/location_accuracy_status_test.dart b/permission_handler_platform_interface/test/src/location_accuracy_status_test.dart new file mode 100644 index 000000000..01560dbe0 --- /dev/null +++ b/permission_handler_platform_interface/test/src/location_accuracy_status_test.dart @@ -0,0 +1,76 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart'; + +void main() { + group('LocationAccuracyStatus', () { + test('LocationAccuracyStatus should contain 3 options', () { + const values = LocationAccuracyStatus.values; + + expect(values.length, 3); + }); + + test('LocationAccuracyStatus enum should have items in correct index', () { + const values = LocationAccuracyStatus.values; + + expect(values[0], LocationAccuracyStatus.reduced); + expect(values[1], LocationAccuracyStatus.precise); + expect(values[2], LocationAccuracyStatus.unknown); + }); + }); + + group('LocationAccuracyStatusValue', () { + test('LocationAccuracyStatusValue returns right integer', () { + expect(LocationAccuracyStatus.reduced.value, 0); + expect(LocationAccuracyStatus.precise.value, 1); + expect(LocationAccuracyStatus.unknown.value, 2); + }); + + test( + // ignore: lines_longer_than_80_chars + 'statusByValue should return right index int that corresponds with the right LocationAccuracyStatus', + () { + expect(LocationAccuracyStatusValue.statusByValue(0), + LocationAccuracyStatus.reduced); + expect(LocationAccuracyStatusValue.statusByValue(1), + LocationAccuracyStatus.precise); + expect(LocationAccuracyStatusValue.statusByValue(2), + LocationAccuracyStatus.unknown); + }); + }); + + group('LocationAccuracyStatusGetters', () { + test('Getters should return true if statement is met', () { + expect(LocationAccuracyStatus.reduced.isReduced, true); + expect(LocationAccuracyStatus.precise.isPrecise, true); + expect(LocationAccuracyStatus.unknown.isUnknown, true); + }); + + test('Getters should return false if statement is not met', () { + expect(LocationAccuracyStatus.reduced.isPrecise, false); + expect(LocationAccuracyStatus.reduced.isUnknown, false); + expect(LocationAccuracyStatus.precise.isReduced, false); + expect(LocationAccuracyStatus.precise.isUnknown, false); + expect(LocationAccuracyStatus.unknown.isReduced, false); + expect(LocationAccuracyStatus.unknown.isPrecise, false); + }); + }); + + group('FutureLocationAccuracyStatusGetters', () { + mockFuture(LocationAccuracyStatus status) => Future.value(status); + + test('Getters should return true if statement is met', () async { + expect(await mockFuture(LocationAccuracyStatus.reduced).isReduced, true); + expect(await mockFuture(LocationAccuracyStatus.precise).isPrecise, true); + expect(await mockFuture(LocationAccuracyStatus.unknown).isUnknown, true); + }); + + test('Getters should return false if statement is not met', () async { + expect(await mockFuture(LocationAccuracyStatus.reduced).isPrecise, false); + expect(await mockFuture(LocationAccuracyStatus.reduced).isUnknown, false); + expect(await mockFuture(LocationAccuracyStatus.precise).isReduced, false); + expect(await mockFuture(LocationAccuracyStatus.precise).isUnknown, false); + expect(await mockFuture(LocationAccuracyStatus.unknown).isReduced, false); + expect(await mockFuture(LocationAccuracyStatus.unknown).isPrecise, false); + }); + }); +} diff --git a/permission_handler_platform_interface/test/src/method_channel/method_channel_permission_handler_test.dart b/permission_handler_platform_interface/test/src/method_channel/method_channel_permission_handler_test.dart index c03c279a1..ccf38fd53 100644 --- a/permission_handler_platform_interface/test/src/method_channel/method_channel_permission_handler_test.dart +++ b/permission_handler_platform_interface/test/src/method_channel/method_channel_permission_handler_test.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart'; import 'package:permission_handler_platform_interface/src/method_channel/method_channel_permission_handler.dart'; @@ -202,4 +203,74 @@ void main() { expect(shouldShowRationale, true); }); }); + + group('getLocationAccuracy:', () { + _testLocationAccuracyMethod( + methodName: 'getLocationAccuracy', + methodUnderTest: () => + MethodChannelPermissionHandler().getLocationAccuracy()); + }); + + group('requestTemporaryFullAccuracy:', () { + _testLocationAccuracyMethod( + methodName: 'requestTemporaryFullAccuracy', + methodUnderTest: () => MethodChannelPermissionHandler() + .requestTemporaryFullAccuracy('test')); + }); +} + +void _testLocationAccuracyMethod({ + required String methodName, + required Future Function() methodUnderTest, +}) { + final supportedPlatforms = [TargetPlatform.iOS]; + group('Unsupported Platforms', () { + final unsupportedPlatforms = TargetPlatform.values + .where((element) => !supportedPlatforms.contains(element)); + + for (final platform in unsupportedPlatforms) { + test('$methodName should return unknown accuracy on $platform', () async { + debugDefaultTargetPlatformOverride = platform; + // We mock the return of a precise location accuracy status + // since this platform does not support location accuracy, + // we expect the result to be unknown. + MethodChannelMock( + channelName: 'flutter.baseflow.com/permissions/methods', + method: methodName, + result: LocationAccuracyStatus.precise.value, + ); + + final accuracy = await methodUnderTest(); + expect(accuracy, LocationAccuracyStatus.unknown); + }); + } + }); + + group('Supported Platform', () { + for (final platform in supportedPlatforms) { + test('$methodName should return precise accuracy on $platform', () async { + debugDefaultTargetPlatformOverride = platform; + MethodChannelMock( + channelName: 'flutter.baseflow.com/permissions/methods', + method: methodName, + result: LocationAccuracyStatus.precise.value, + ); + + final accuracy = await methodUnderTest(); + expect(accuracy, LocationAccuracyStatus.precise); + }); + + test('$methodName should return reduced accuracy on $platform', () async { + debugDefaultTargetPlatformOverride = platform; + MethodChannelMock( + channelName: 'flutter.baseflow.com/permissions/methods', + method: methodName, + result: LocationAccuracyStatus.reduced.value, + ); + + final accuracy = await methodUnderTest(); + expect(accuracy, LocationAccuracyStatus.reduced); + }); + } + }); } diff --git a/permission_handler_platform_interface/test/src/method_channel/utils/coded_test.dart b/permission_handler_platform_interface/test/src/method_channel/utils/coded_test.dart index 1742b9fc6..86fed65d6 100644 --- a/permission_handler_platform_interface/test/src/method_channel/utils/coded_test.dart +++ b/permission_handler_platform_interface/test/src/method_channel/utils/coded_test.dart @@ -32,5 +32,10 @@ void main() { expect(integers.first, isA()); }); + + test('decodeLocationAccuracyStatus should return a LocationAccuracyStatus', + () { + expect(decodeLocationAccuracyStatus(0), LocationAccuracyStatus.reduced); + }); }); } diff --git a/permission_handler_platform_interface/test/src/permission_handler_platform_interface_test.dart b/permission_handler_platform_interface/test/src/permission_handler_platform_interface_test.dart index c965e72ed..9d82ac08c 100644 --- a/permission_handler_platform_interface/test/src/permission_handler_platform_interface_test.dart +++ b/permission_handler_platform_interface/test/src/permission_handler_platform_interface_test.dart @@ -85,6 +85,28 @@ void main() { Permission.accessMediaLocation); }, throwsUnimplementedError); }); + + test( + // ignore: lines_longer_than_80_chars + 'Default implementation of getLocationAccuracy should throw unimplemented error', + () { + final permissionHandlerPlatform = ExtendsPermissionHandlerPlatform(); + + expect(() { + permissionHandlerPlatform.getLocationAccuracy(); + }, throwsUnimplementedError); + }); + + test( + // ignore: lines_longer_than_80_chars + 'Default implementation of requestTemporaryFullAccuracy should throw unimplemented error', + () { + final permissionHandlerPlatform = ExtendsPermissionHandlerPlatform(); + + expect(() { + permissionHandlerPlatform.requestTemporaryFullAccuracy('any'); + }, throwsUnimplementedError); + }); }); }