diff --git a/ios/.gitignore b/darwin/.gitignore similarity index 100% rename from ios/.gitignore rename to darwin/.gitignore diff --git a/ios/Assets/.gitkeep b/darwin/Assets/.gitkeep similarity index 100% rename from ios/Assets/.gitkeep rename to darwin/Assets/.gitkeep diff --git a/ios/Classes/Contact.swift b/darwin/Classes/Contact.swift similarity index 100% rename from ios/Classes/Contact.swift rename to darwin/Classes/Contact.swift diff --git a/darwin/Classes/ContactViewIOS.swift b/darwin/Classes/ContactViewIOS.swift new file mode 100644 index 00000000..0df12c78 --- /dev/null +++ b/darwin/Classes/ContactViewIOS.swift @@ -0,0 +1,55 @@ +#if os(iOS) +import Flutter +import UIKit +import Contacts +import ContactsUI + +@available(iOS 9.0, *) +@available(macOS, unavailable) +public class ContactViewIOS: NSObject, CNContactViewControllerDelegate, CNContactPickerDelegate { + + private let flutterContactsPlugin: FlutterContactsPlugin + public var rootViewController: UIViewController + + init(_ flutterContactsPlugin: FlutterContactsPlugin) { + self.flutterContactsPlugin = flutterContactsPlugin; + rootViewController = UIApplication.shared.delegate!.window!!.rootViewController! + } + + public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { + if let result = flutterContactsPlugin.externalResult { + result(contact?.identifier) + flutterContactsPlugin.externalResult = nil + } + viewController.dismiss(animated: true, completion: nil) + } + + public func contactPicker(_: CNContactPickerViewController, didSelect contact: CNContact) { + if let result = flutterContactsPlugin.externalResult { + result(contact.identifier) + flutterContactsPlugin.externalResult = nil + } + } + + // public func contactPicker(_: CNContactPicker, didSelect contact: CNContact) { + // if let result = flutterContactsPlugin.externalResult { + // result(contact.identifier) + // flutterContactsPlugin.externalResult = nil + // } + // } + + public func contactPickerDidCancel(_: CNContactPickerViewController) { + if let result = flutterContactsPlugin.externalResult { + result(nil) + flutterContactsPlugin.externalResult = nil + } + } + + // public func contactPickerDidCancel(_: CNContactPicker) { + // if let result = flutterContactsPlugin.externalResult { + // result(nil) + // flutterContactsPlugin.externalResult = nil + // } + // } +} +#endif diff --git a/ios/Classes/SwiftFlutterContactsPlugin.swift b/darwin/Classes/FlutterContactsPlugin.swift similarity index 84% rename from ios/Classes/SwiftFlutterContactsPlugin.swift rename to darwin/Classes/FlutterContactsPlugin.swift index d486b592..6c77edbb 100644 --- a/ios/Classes/SwiftFlutterContactsPlugin.swift +++ b/darwin/Classes/FlutterContactsPlugin.swift @@ -1,9 +1,15 @@ +#if os(macOS) + import Cocoa + import FlutterMacOS +#endif +#if os(iOS) + import Flutter + import UIKit +#endif import Contacts import ContactsUI -import Flutter -import UIKit -@available(iOS 9.0, *) +@available(iOS 9.0, macOS 10.11, *) public enum FlutterContacts { // Fetches contact(s). static func selectInternal( @@ -44,12 +50,12 @@ public enum FlutterContacts { CNContactBirthdayKey, CNContactDatesKey, ] - if #available(iOS 10, *) { + if #available(iOS 10, macOS 10.12, *) { keys.append(CNContactPhoneticOrganizationNameKey) } // Notes need explicit entitlement from Apple starting with iOS13. // https://stackoverflow.com/questions/57442114/ios-13-cncontacts-no-longer-working-to-retrieve-all-contacts - if #available(iOS 13, *), !includeNotesOnIos13AndAbove {} else { + if #available(iOS 13, macOS 10.11, *), !includeNotesOnIos13AndAbove {} else { keys.append(CNContactNoteKey) } if externalIntent { @@ -234,13 +240,13 @@ public enum FlutterContacts { CNContactThumbnailImageDataKey, CNContactImageDataKey, ] - if #available(iOS 10, *) { keys.append(CNContactPhoneticOrganizationNameKey) } - if #available(iOS 13, *), !includeNotesOnIos13AndAbove {} else { + if #available(iOS 10, macOS 10.12,*) { keys.append(CNContactPhoneticOrganizationNameKey) } + if #available(iOS 13, macOS 10.11, *), !includeNotesOnIos13AndAbove {} else { keys.append(CNContactNoteKey) } let request = CNContactFetchRequest(keysToFetch: keys as! [CNKeyDescriptor]) - if #available(iOS 10, *) { request.mutableObjects = true } + if #available(iOS 10, macOS 10.12, *) { request.mutableObjects = true } request.predicate = CNContact.predicateForContacts(withIdentifiers: [id]) let store = CNContactStore() var contacts: [CNContact] = [] @@ -407,7 +413,7 @@ public enum FlutterContacts { (args["events"] as! [[String: Any]]).forEach { Event(fromMap: $0).addTo(contact) } - if #available(iOS 13, *), !includeNotesOnIos13AndAbove {} else { + if #available(iOS 13, macOS 10.11, *), !includeNotesOnIos13AndAbove {} else { if let note = (args["notes"] as! [[String: Any]]).first { Note(fromMap: note).addTo(contact) } @@ -418,32 +424,75 @@ public enum FlutterContacts { } } -@available(iOS 9.0, *) -public class SwiftFlutterContactsPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, CNContactViewControllerDelegate, CNContactPickerDelegate { - private let rootViewController: UIViewController - private var externalResult: FlutterResult? +@available(iOS 9.0, macOS 10.11, *) +public class FlutterContactsPlugin: NSObject, FlutterPlugin, FlutterStreamHandler { + + #if os(iOS) + private var contactViewIOS: ContactViewIOS? + #endif + public var externalResult: FlutterResult? public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel( - name: "github.com/QuisApp/flutter_contacts", - binaryMessenger: registrar.messenger() - ) - let eventChannel = FlutterEventChannel( - name: "github.com/QuisApp/flutter_contacts/events", - binaryMessenger: registrar.messenger() - ) - let rootViewController = UIApplication.shared.delegate!.window!!.rootViewController! - let instance = SwiftFlutterContactsPlugin(rootViewController) + #if os(macOS) + let channel = FlutterMethodChannel( + name: "github.com/QuisApp/flutter_contacts", + binaryMessenger: registrar.messenger + ) + let eventChannel = FlutterEventChannel( + name: "github.com/QuisApp/flutter_contacts/events", + binaryMessenger: registrar.messenger + ) + let instance = FlutterContactsPlugin() + #endif + #if os(iOS) + let channel = FlutterMethodChannel( + name: "github.com/QuisApp/flutter_contacts", + binaryMessenger: registrar.messenger() + ) + let eventChannel = FlutterEventChannel( + name: "github.com/QuisApp/flutter_contacts/events", + binaryMessenger: registrar.messenger() + ) + + //private let contactViewIOS: ContactViewIOS? + //contactViewIOS = ContactViewIOS(instance) + + let instance = FlutterContactsPlugin() + instance.contactViewIOS = ContactViewIOS(instance) + #endif + registrar.addMethodCallDelegate(instance, channel: channel) eventChannel.setStreamHandler(instance) } - init(_ rootViewController: UIViewController) { - self.rootViewController = rootViewController - } + // #if os(iOS) + // @available(iOS 9.0, *) + // init(_ contactViewIOS: ContactViewIOS?) { + // self.contactViewIOS = contactViewIOS + // } + // #endif + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { + case "authorisationStatus": + DispatchQueue.global(qos: .userInteractive).async { + let status = CNContactStore.authorizationStatus(for: .contacts) + switch status { + case .notDetermined: + result("notDetermined"); + case .restricted: + result("restricted"); + case .denied: + result("denied"); + case .authorized: + result("authorized"); + case .limited: + result("limited"); + default: + result("unknown"); + } + } case "requestPermission": DispatchQueue.global(qos: .userInteractive).async { CNContactStore().requestAccess(for: .contacts, completionHandler: { (granted, _) -> Void in @@ -583,6 +632,14 @@ public class SwiftFlutterContactsPlugin: NSObject, FlutterPlugin, FlutterStreamH } } case "openExternalViewOrEdit": + #if os(macOS) + result(FlutterError( + code: "macos.notImplemented", + message: "openExternalViewOrEdit is not implemented on MacOS", + details: "openExternalViewOrEdit is not implemented on MacOS" + )) + #endif + #if os(iOS) DispatchQueue.main.async { let args = call.arguments as! [Any?] let id = args[0] as! String @@ -604,21 +661,39 @@ public class SwiftFlutterContactsPlugin: NSObject, FlutterPlugin, FlutterStreamH target: self, action: #selector(self.contactViewControllerDidCancel) ) - contactView.delegate = self + contactView.delegate = self.contactViewIOS! // https://stackoverflow.com/a/39594589 let navigationController = UINavigationController(rootViewController: contactView) - self.rootViewController.present(navigationController, animated: true, completion: nil) + self.contactViewIOS!.rootViewController.present(navigationController, animated: true, completion: nil) self.externalResult = result } } + #endif case "openExternalPick": + #if os(macOS) + result(FlutterError( + code: "macos.notImplemented", + message: "openExternalPick is not implemented on MacOS", + details: "openExternalPick is not implemented on MacOS" + )) + #endif + #if os(iOS) DispatchQueue.main.async { let contactPicker = CNContactPickerViewController() - contactPicker.delegate = self - self.rootViewController.present(contactPicker, animated: true, completion: nil) + contactPicker.delegate = self.contactViewIOS! + self.contactViewIOS!.rootViewController.present(contactPicker, animated: true, completion: nil) self.externalResult = result } + #endif case "openExternalInsert": + #if os(macOS) + result(FlutterError( + code: "macos.notImplemented", + message: "openExternalInsert is not implemented on MacOS", + details: "openExternalInsert is not implemented on MacOS" + )) + #endif + #if os(iOS) DispatchQueue.main.async { let contact = CNMutableContact() let args = call.arguments as? [Any?] @@ -636,12 +711,13 @@ public class SwiftFlutterContactsPlugin: NSObject, FlutterPlugin, FlutterStreamH target: self, action: #selector(self.contactViewControllerDidCancel) ) - contactView.delegate = self + contactView.delegate = self.contactViewIOS! // https://stackoverflow.com/a/39594589 let navigationController = UINavigationController(rootViewController: contactView) - self.rootViewController.present(navigationController, animated: true, completion: nil) + self.contactViewIOS!.rootViewController.present(navigationController, animated: true, completion: nil) self.externalResult = result } + #endif default: result(FlutterMethodNotImplemented) } @@ -665,14 +741,7 @@ public class SwiftFlutterContactsPlugin: NSObject, FlutterPlugin, FlutterStreamH return nil } - public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { - if let result = externalResult { - result(contact?.identifier) - externalResult = nil - } - viewController.dismiss(animated: true, completion: nil) - } - + #if os(iOS) @objc func contactViewControllerDidCancel() { if let result = externalResult { let viewController: UIViewController? = UIApplication.shared.delegate?.window??.rootViewController @@ -681,18 +750,7 @@ public class SwiftFlutterContactsPlugin: NSObject, FlutterPlugin, FlutterStreamH externalResult = nil } } + #endif - public func contactPicker(_: CNContactPickerViewController, didSelect contact: CNContact) { - if let result = externalResult { - result(contact.identifier) - externalResult = nil - } - } - - public func contactPickerDidCancel(_: CNContactPickerViewController) { - if let result = externalResult { - result(nil) - externalResult = nil - } - } + } diff --git a/ios/Classes/properties/Account.swift b/darwin/Classes/properties/Account.swift similarity index 100% rename from ios/Classes/properties/Account.swift rename to darwin/Classes/properties/Account.swift diff --git a/ios/Classes/properties/Address.swift b/darwin/Classes/properties/Address.swift similarity index 96% rename from ios/Classes/properties/Address.swift rename to darwin/Classes/properties/Address.swift index cc2f431b..03841f80 100644 --- a/ios/Classes/properties/Address.swift +++ b/darwin/Classes/properties/Address.swift @@ -45,7 +45,7 @@ struct Address { case CNLabelOther: label = "other" default: - if #available(iOS 13, *), a.label == CNLabelSchool { + if #available(iOS 13, macOS 15, *), a.label == CNLabelSchool { label = "school" } else { label = "custom" @@ -59,7 +59,7 @@ struct Address { postalCode = a.value.postalCode country = a.value.country isoCountry = a.value.isoCountryCode - if #available(iOS 13, *) { + if #available(iOS 13, macOS 15, *) { subAdminArea = a.value.subAdministrativeArea subLocality = a.value.subLocality } @@ -126,7 +126,7 @@ struct Address { case "home": labelInv = CNLabelHome case "school": - if #available(iOS 13, *) { + if #available(iOS 13, macOS 15, *) { labelInv = CNLabelSchool } else { labelInv = "school" diff --git a/ios/Classes/properties/Email.swift b/darwin/Classes/properties/Email.swift similarity index 93% rename from ios/Classes/properties/Email.swift rename to darwin/Classes/properties/Email.swift index 7fa7fca0..4c96055c 100644 --- a/ios/Classes/properties/Email.swift +++ b/darwin/Classes/properties/Email.swift @@ -27,7 +27,7 @@ struct Email { case CNLabelOther: label = "other" default: - if #available(iOS 13, *), e.label == CNLabelSchool { + if #available(iOS 13, macOS 15, *), e.label == CNLabelSchool { label = "school" } else { label = "custom" @@ -52,7 +52,7 @@ struct Email { case "iCloud": labelInv = CNLabelEmailiCloud case "school": - if #available(iOS 13, *) { + if #available(iOS 13, macOS 15, *) { labelInv = CNLabelSchool } else { labelInv = "school" diff --git a/ios/Classes/properties/Event.swift b/darwin/Classes/properties/Event.swift similarity index 100% rename from ios/Classes/properties/Event.swift rename to darwin/Classes/properties/Event.swift diff --git a/ios/Classes/properties/Group.swift b/darwin/Classes/properties/Group.swift similarity index 100% rename from ios/Classes/properties/Group.swift rename to darwin/Classes/properties/Group.swift diff --git a/ios/Classes/properties/Name.swift b/darwin/Classes/properties/Name.swift similarity index 100% rename from ios/Classes/properties/Name.swift rename to darwin/Classes/properties/Name.swift diff --git a/ios/Classes/properties/Note.swift b/darwin/Classes/properties/Note.swift similarity index 100% rename from ios/Classes/properties/Note.swift rename to darwin/Classes/properties/Note.swift diff --git a/ios/Classes/properties/Organization.swift b/darwin/Classes/properties/Organization.swift similarity index 100% rename from ios/Classes/properties/Organization.swift rename to darwin/Classes/properties/Organization.swift diff --git a/ios/Classes/properties/Phone.swift b/darwin/Classes/properties/Phone.swift similarity index 96% rename from ios/Classes/properties/Phone.swift rename to darwin/Classes/properties/Phone.swift index 827e74db..503c9e46 100644 --- a/ios/Classes/properties/Phone.swift +++ b/darwin/Classes/properties/Phone.swift @@ -44,7 +44,7 @@ struct Phone { case CNLabelOther: label = "other" default: - if #available(iOS 13, *), p.label == CNLabelSchool { + if #available(iOS 13, macOS 15, *), p.label == CNLabelSchool { label = "school" } else { label = "custom" @@ -82,7 +82,7 @@ struct Phone { case "pager": labelInv = CNLabelPhoneNumberPager case "school": - if #available(iOS 13, *) { + if #available(iOS 13, macOS 15, *) { labelInv = CNLabelSchool } else { labelInv = "school" diff --git a/ios/Classes/properties/SocialMedia.swift b/darwin/Classes/properties/SocialMedia.swift similarity index 100% rename from ios/Classes/properties/SocialMedia.swift rename to darwin/Classes/properties/SocialMedia.swift diff --git a/ios/Classes/properties/Website.swift b/darwin/Classes/properties/Website.swift similarity index 93% rename from ios/Classes/properties/Website.swift rename to darwin/Classes/properties/Website.swift index c4af56fb..09c1b81b 100644 --- a/ios/Classes/properties/Website.swift +++ b/darwin/Classes/properties/Website.swift @@ -25,7 +25,7 @@ struct Website { case CNLabelOther: label = "other" default: - if #available(iOS 13, *), w.label == CNLabelSchool { + if #available(iOS 13, macOS 15, *), w.label == CNLabelSchool { label = "school" } else { label = "custom" @@ -49,7 +49,7 @@ struct Website { case "homepage": labelInv = CNLabelURLAddressHomePage case "school": - if #available(iOS 13, *) { + if #available(iOS 13, macOS 15, *) { labelInv = CNLabelSchool } else { labelInv = "school" diff --git a/ios/flutter_contacts.podspec b/darwin/flutter_contacts.podspec similarity index 81% rename from ios/flutter_contacts.podspec rename to darwin/flutter_contacts.podspec index f263d21d..1a5789fd 100644 --- a/ios/flutter_contacts.podspec +++ b/darwin/flutter_contacts.podspec @@ -12,10 +12,15 @@ A new flutter plugin project. s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } s.source_files = 'Classes/**/*' - s.dependency 'Flutter' - s.platform = :ios, '8.0' + # s.dependency 'Flutter' + s.ios.dependency 'Flutter' + s.osx.dependency 'FlutterMacOS' + # s.platform = :ios, '8.0' + s.ios.deployment_target = '11.0' + s.osx.deployment_target = '10.14' # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } diff --git a/example_full/ios/Podfile.lock b/example_full/ios/Podfile.lock index c099f652..d02b8145 100644 --- a/example_full/ios/Podfile.lock +++ b/example_full/ios/Podfile.lock @@ -2,25 +2,26 @@ PODS: - Flutter (1.0.0) - flutter_contacts (0.0.1): - Flutter + - FlutterMacOS - image_picker_ios (0.0.1): - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - - flutter_contacts (from `.symlinks/plugins/flutter_contacts/ios`) + - flutter_contacts (from `.symlinks/plugins/flutter_contacts/darwin`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) EXTERNAL SOURCES: Flutter: :path: Flutter flutter_contacts: - :path: ".symlinks/plugins/flutter_contacts/ios" + :path: ".symlinks/plugins/flutter_contacts/darwin" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_contacts: edb1c5ce76aa433e20e6cb14c615f4c0b66e0983 + flutter_contacts: 03984bf5bd2052b169e8bb64cb1ee3c7eb2d17fe image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 diff --git a/example_full/ios/Runner/AppDelegate.swift b/example_full/ios/Runner/AppDelegate.swift index 17562970..e4c5e5fd 100644 --- a/example_full/ios/Runner/AppDelegate.swift +++ b/example_full/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/ios/Classes/FlutterContactsPlugin.h b/ios/Classes/FlutterContactsPlugin.h deleted file mode 100644 index 852e6164..00000000 --- a/ios/Classes/FlutterContactsPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface FlutterContactsPlugin : NSObject -@end diff --git a/ios/Classes/FlutterContactsPlugin.m b/ios/Classes/FlutterContactsPlugin.m deleted file mode 100644 index 8800d563..00000000 --- a/ios/Classes/FlutterContactsPlugin.m +++ /dev/null @@ -1,15 +0,0 @@ -#import "FlutterContactsPlugin.h" -#if __has_include() -#import -#else -// Support project import fallback if the generated compatibility header -// is not copied when this plugin is created as a library. -// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 -#import "flutter_contacts-Swift.h" -#endif - -@implementation FlutterContactsPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftFlutterContactsPlugin registerWithRegistrar:registrar]; -} -@end diff --git a/lib/flutter_contacts.dart b/lib/flutter_contacts.dart index d2e8aac7..fe72adbe 100644 --- a/lib/flutter_contacts.dart +++ b/lib/flutter_contacts.dart @@ -35,6 +35,17 @@ class FlutterContacts { /// Plugin configuration. static var config = FlutterContactsConfig(); + /// Allows the checking of the current authorisation status of access to the + /// contacts. Returns the enum raw value of notDetermined, restricted, denied, + /// authorized or limited. + static Future authorisationStatus() async { + if (Platform.isIOS || Platform.isMacOS) { + String status = await _channel.invokeMethod('authorisationStatus'); + return status; + } + throw Exception('authorisationStatus not implemented for this platform.'); + } + /// Requests permission to read or read/write contacts. Returns true if /// granted, false in any other case. Note: read-only mode is only applicable /// to Android. diff --git a/pubspec.yaml b/pubspec.yaml index 241810f3..bd642443 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,3 +25,8 @@ flutter: pluginClass: FlutterContactsPlugin ios: pluginClass: FlutterContactsPlugin + sharedDarwinSource: true + macos: + pluginClass: FlutterContactsPlugin + sharedDarwinSource: true +