diff --git a/location/CHANGELOG.md b/location/CHANGELOG.md index 199533b6..c91b6d2b 100644 --- a/location/CHANGELOG.md +++ b/location/CHANGELOG.md @@ -1,3 +1,7 @@ +## [3.0.1] 27th March 2020 + +- Fix a crash happening during iOS build + ## [3.0.0] 26th March 2020 - Add Web and macOS as new supported platforms (huge thanks to long1eu) diff --git a/location/darwin/Classes/LocationPlugin.h b/location/darwin/Classes/LocationPlugin.h new file mode 100644 index 00000000..42cb7ebb --- /dev/null +++ b/location/darwin/Classes/LocationPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface LocationPlugin : NSObject +@end diff --git a/location/example/ios/Runner.xcodeproj/project.pbxproj b/location/example/ios/Runner.xcodeproj/project.pbxproj index 0880d297..e13908f5 100644 --- a/location/example/ios/Runner.xcodeproj/project.pbxproj +++ b/location/example/ios/Runner.xcodeproj/project.pbxproj @@ -248,18 +248,9 @@ files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../Flutter/Flutter.framework", - "${BUILT_PRODUCTS_DIR}/e2e/e2e.framework", - "${BUILT_PRODUCTS_DIR}/location/location.framework", - "${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/e2e.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/location.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/location/ios/Classes/LocationPlugin.m b/location/ios/Classes/LocationPlugin.m deleted file mode 120000 index df3c5794..00000000 --- a/location/ios/Classes/LocationPlugin.m +++ /dev/null @@ -1 +0,0 @@ -darwin/Classes/LocationPlugin.m \ No newline at end of file diff --git a/location/ios/Classes/LocationPlugin.m b/location/ios/Classes/LocationPlugin.m new file mode 100644 index 00000000..6ae7ca83 --- /dev/null +++ b/location/ios/Classes/LocationPlugin.m @@ -0,0 +1,321 @@ +#import "LocationPlugin.h" + +#ifdef COCOAPODS +@import CoreLocation; +#else +#import +#endif + +@interface LocationPlugin() +@property (strong, nonatomic) CLLocationManager *clLocationManager; +@property (copy, nonatomic) FlutterResult flutterResult; +@property (assign, nonatomic) BOOL locationWanted; +@property (assign, nonatomic) BOOL permissionWanted; + +@property (copy, nonatomic) FlutterEventSink flutterEventSink; +@property (assign, nonatomic) BOOL flutterListening; +@property (assign, nonatomic) BOOL hasInit; +@end + +@implementation LocationPlugin + ++(void)registerWithRegistrar:(NSObject*)registrar { + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"lyokone/location" + binaryMessenger:registrar.messenger]; + FlutterEventChannel *stream = + [FlutterEventChannel eventChannelWithName:@"lyokone/locationstream" + binaryMessenger:registrar.messenger]; + + LocationPlugin *instance = [[LocationPlugin alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; + [stream setStreamHandler:instance]; +} + +-(instancetype)init { + self = [super init]; + + if (self) { + self.locationWanted = NO; + self.permissionWanted = NO; + self.flutterListening = NO; + self.hasInit = NO; + } + return self; +} + +-(void)initLocation { + if (!(self.hasInit)) { + self.hasInit = YES; + + if ([CLLocationManager locationServicesEnabled]) { + self.clLocationManager = [[CLLocationManager alloc] init]; + self.clLocationManager.delegate = self; + self.clLocationManager.desiredAccuracy = kCLLocationAccuracyBest; + } + } +} + +-(void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + [self initLocation]; + if ([call.method isEqualToString:@"changeSettings"]) { + if ([CLLocationManager locationServicesEnabled]) { + NSDictionary *dictionary = @{ + @"0" : @(kCLLocationAccuracyKilometer), + @"1" : @(kCLLocationAccuracyHundredMeters), + @"2" : @(kCLLocationAccuracyNearestTenMeters), + @"3" : @(kCLLocationAccuracyBest), + @"4" : @(kCLLocationAccuracyBestForNavigation) + }; + + self.clLocationManager.desiredAccuracy = + [dictionary[call.arguments[@"accuracy"]] doubleValue]; + double distanceFilter = [call.arguments[@"distanceFilter"] doubleValue]; + if (distanceFilter == 0){ + distanceFilter = kCLDistanceFilterNone; + } + self.clLocationManager.distanceFilter = distanceFilter; + result(@1); + } + } else if ([call.method isEqualToString:@"getLocation"]) { + if (![CLLocationManager locationServicesEnabled]) { + result([FlutterError errorWithCode:@"SERVICE_STATUS_DISABLED" message:@"Failed to get location. Location services disabled" details:nil]); + return; + } + if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { + // Location services are requested but user has denied + NSString *message = @"The user explicitly denied the use of location services for this " + "app or location services are currently disabled in Settings."; + result([FlutterError errorWithCode:@"PERMISSION_DENIED" + message:message + details:nil]); + return; + } + + self.flutterResult = result; + self.locationWanted = YES; + + if ([self isPermissionGranted]) { + [self.clLocationManager startUpdatingLocation]; + } else { + [self requestPermission]; + if ([self isPermissionGranted]) { + [self.clLocationManager startUpdatingLocation]; + } + } + } else if ([call.method isEqualToString:@"hasPermission"]) { + if ([self isPermissionGranted]) { + result(@1); + } else { + result(@0); + } + } else if ([call.method isEqualToString:@"requestPermission"]) { + if ([self isPermissionGranted]) { + result(@1); + } else if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) { + self.flutterResult = result; + self.permissionWanted = YES; + [self requestPermission]; + } else { + result(@2); + } + } else if ([call.method isEqualToString:@"serviceEnabled"]) { + if ([CLLocationManager locationServicesEnabled]) { + result(@1); + } else { + result(@0); + } + } else if ([call.method isEqualToString:@"requestService"]) { + if ([CLLocationManager locationServicesEnabled]) { + result(@1); + } else { +#if TARGET_OS_OSX + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Location is Disabled"]; + [alert setInformativeText:@"To use location, go to your System Preferences > Security & Privacy > Privacy > Location Services."]; + [alert addButtonWithTitle:@"Open"]; + [alert addButtonWithTitle:@"Cancel"]; + [alert beginSheetModalForWindow:NSApplication.sharedApplication.mainWindow + completionHandler:^(NSModalResponse returnCode) { + if (returnCode == NSAlertFirstButtonReturn) { + NSString *urlString = @"x-apple.systempreferences:com.apple.preference.security?Privacy_LocationServices"; + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]]; + } else { + NSLog(@"Cancel"); + } + }]; +#else + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Location is Disabled" + message:@"To use location, go to your Settings App > Privacy > Location Services." + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:nil]; + [alert show]; +#endif + result(@0); + } + } else { + result(FlutterMethodNotImplemented); + } +} + + +-(void) requestPermission { +#if TARGET_OS_OSX + if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] != nil) { + if (@available(macOS 10.15, *)) { + [self.clLocationManager requestAlwaysAuthorization]; + } + } +#else + if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] != nil) { + [self.clLocationManager requestWhenInUseAuthorization]; + } + else if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] != nil) { + [self.clLocationManager requestAlwaysAuthorization]; + } +#endif + else { + [NSException raise:NSInternalInconsistencyException format: + @"To use location in iOS8 and above you need to define either " + "NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in the app " + "bundle's Info.plist file"]; + } +} + +-(BOOL) isPermissionGranted { + BOOL isPermissionGranted = NO; + CLAuthorizationStatus status = [CLLocationManager authorizationStatus]; + +#if TARGET_OS_OSX + if (status == kCLAuthorizationStatusAuthorized) { + // Location services are available + isPermissionGranted = YES; + } else if (@available(macOS 10.12, *)) { + if (status == kCLAuthorizationStatusAuthorizedAlways) { + // Location services are available + isPermissionGranted = YES; + } + } +#else //if TARGET_OS_IOS + if (status == kCLAuthorizationStatusAuthorizedWhenInUse || + status == kCLAuthorizationStatusAuthorizedAlways) { + // Location services are available + isPermissionGranted = YES; + } +#endif + else if (status == kCLAuthorizationStatusDenied || + status == kCLAuthorizationStatusRestricted) { + // Location services are requested but user has denied / the app is restricted from + // getting location + isPermissionGranted = NO; + } else if (status == kCLAuthorizationStatusNotDetermined) { + // Location services never requested / the user still haven't decide + isPermissionGranted = NO; + } else { + isPermissionGranted = NO; + } + + return isPermissionGranted; +} + +-(FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events { + self.flutterEventSink = events; + self.flutterListening = YES; + + if ([self isPermissionGranted]) { + [self.clLocationManager startUpdatingLocation]; + } else { + [self requestPermission]; + } + + return nil; +} + +-(FlutterError*)onCancelWithArguments:(id)arguments { + self.flutterListening = NO; + [self.clLocationManager stopUpdatingLocation]; + return nil; +} + +#pragma mark - CLLocationManagerDelegate Methods + +-(void)locationManager:(CLLocationManager*)manager + didUpdateLocations:(NSArray*)locations { + CLLocation *location = locations.firstObject; + NSTimeInterval timeInSeconds = [location.timestamp timeIntervalSince1970]; + NSDictionary* coordinatesDict = + @{ + @"latitude": @(location.coordinate.latitude), + @"longitude": @(location.coordinate.longitude), + @"accuracy": @(location.horizontalAccuracy), + @"altitude": @(location.altitude), + @"speed": @(location.speed), + @"speed_accuracy": @0.0, + @"heading": @(location.course), + @"time": @(((double) timeInSeconds) * 1000.0) // in milliseconds since the epoch + }; + + if (self.locationWanted) { + self.locationWanted = NO; + self.flutterResult(coordinatesDict); + } + if (self.flutterListening) { + self.flutterEventSink(coordinatesDict); + } else { + [self.clLocationManager stopUpdatingLocation]; + } +} + +- (void)locationManager:(CLLocationManager *)manager + didChangeAuthorizationStatus:(CLAuthorizationStatus)status { + if (status == kCLAuthorizationStatusDenied) { + // The user denied authorization + NSLog(@"User denied permissions"); + if (self.permissionWanted) { + self.permissionWanted = NO; + self.flutterResult(@0); + } + } +#if TARGET_OS_OSX + else if (status == kCLAuthorizationStatusAuthorized) { + NSLog(@"User granted permissions"); + if (self.permissionWanted) { + self.permissionWanted = NO; + self.flutterResult(@1); + } + + if (self.locationWanted || self.flutterListening) { + [self.clLocationManager startUpdatingLocation]; + } + } else if (@available(macOS 10.12, *)) { + if (status == kCLAuthorizationStatusAuthorizedAlways) { + NSLog(@"User granted permissions"); + if (self.permissionWanted) { + self.permissionWanted = NO; + self.flutterResult(@1); + } + + if (self.locationWanted || self.flutterListening) { + [self.clLocationManager startUpdatingLocation]; + } + } + } +#else //if TARGET_OS_IOS + else if (status == kCLAuthorizationStatusAuthorizedWhenInUse || + status == kCLAuthorizationStatusAuthorizedAlways) { + NSLog(@"User granted permissions"); + if (self.permissionWanted) { + self.permissionWanted = NO; + self.flutterResult(@1); + } + + if (self.locationWanted || self.flutterListening) { + [self.clLocationManager startUpdatingLocation]; + } + } +#endif +} + + +@end diff --git a/location/pubspec.yaml b/location/pubspec.yaml index 92da870d..19d8fa9a 100644 --- a/location/pubspec.yaml +++ b/location/pubspec.yaml @@ -1,6 +1,6 @@ name: location description: A Flutter plugin to easily handle realtime location in iOS and Android. Provides settings for optimizing performance or battery. -version: 3.0.0 +version: 3.0.1 author: Guillaume Bernos homepage: https://github.com/Lyokone/flutterlocation