diff --git a/.gitignore b/.gitignore index 41c1683..1247b75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store .atom/ .idea +.vscode .packages .dart_tool/ .pub/ diff --git a/android/build.gradle b/android/build.gradle index 8fc020d..5b17ada 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -10,7 +10,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.1.3' } } @@ -26,12 +26,11 @@ rootProject.allprojects { apply plugin: 'com.android.library' android { - compileSdkVersion 25 - buildToolsVersion '25.0.3' + compileSdkVersion 27 defaultConfig { minSdkVersion 16 - targetSdkVersion 25 + targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 9910423..819906c 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,3 +1,6 @@ + package="net.goderbauer.flutter.contactpicker"> + + + diff --git a/android/src/main/java/net/goderbauer/flutter/contactpicker/ContactPickerPlugin.java b/android/src/main/java/net/goderbauer/flutter/contactpicker/ContactPickerPlugin.java index 1c4c373..48b9577 100644 --- a/android/src/main/java/net/goderbauer/flutter/contactpicker/ContactPickerPlugin.java +++ b/android/src/main/java/net/goderbauer/flutter/contactpicker/ContactPickerPlugin.java @@ -10,12 +10,15 @@ import android.net.Uri; import android.provider.ContactsContract; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; +import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry.Registrar; @@ -29,9 +32,9 @@ public static void registerWith(Registrar registrar) { channel.setMethodCallHandler(instance); } - private ContactPickerPlugin(Activity activity) { - this.activity = activity; - } + private ContactPickerPlugin(Activity activity) { + this.activity = activity; + } private static int PICK_CONTACT = 2015; @@ -47,7 +50,7 @@ public void onMethodCall(MethodCall call, Result result) { } pendingResult = result; - Intent i = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI); + Intent i = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI); activity.startActivityForResult(i, PICK_CONTACT); } else { result.notImplemented(); @@ -59,31 +62,121 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode != PICK_CONTACT) { return false; } - if (resultCode != RESULT_OK) { + + if (resultCode != RESULT_OK || data.getData() == null) { pendingResult.success(null); pendingResult = null; return true; } + Uri contactUri = data.getData(); - Cursor cursor = activity.getContentResolver().query(contactUri, null, null, null, null); - cursor.moveToFirst(); + Cursor cursor = null; + + try { + cursor = activity.getContentResolver() + .query(contactUri, null, null, null, null); + + if (cursor == null || !cursor.moveToFirst()) { + pendingResult.success(null); + pendingResult = null; + return true; + } + + long contactId = cursor.getLong(cursor.getColumnIndex(ContactsContract.Contacts._ID)); + + List> ims = new ArrayList<>(); + List> emails = new ArrayList<>(); + List> phones = new ArrayList<>(); + List> addresses = new ArrayList<>(); + + Cursor rawCursor = null; + try { + rawCursor = activity.getContentResolver() + .query(ContactsContract.RawContactsEntity.CONTENT_URI, null, ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?", new String[]{Long.toString(contactId)}, null, null); + + if (rawCursor != null && rawCursor.moveToFirst()) { + do { + Map row = new HashMap<>(); + + String mimeType = rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.RawContactsEntity.MIMETYPE)); + switch (mimeType) { + case ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE: { + int type = rawCursor.getInt(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.TYPE)); + String customLabel = rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.LABEL)); + + row.put("label", ContactsContract.CommonDataKinds.Im.getTypeLabel(activity.getResources(), type, customLabel)); + row.put("im", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); + + int protocol = rawCursor.getInt(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.PROTOCOL)); + String customProtocol = rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL)); + row.put("protocol", ContactsContract.CommonDataKinds.Im.getProtocolLabel(activity.getResources(), protocol, customProtocol)); + + ims.add(row); + break; + } + case ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE: { + int type = rawCursor.getInt(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.TYPE)); + String customLabel = rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.LABEL)); + + row.put("label", ContactsContract.CommonDataKinds.Email.getTypeLabel(activity.getResources(), type, customLabel)); + row.put("email", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS))); + + emails.add(row); + break; + } + case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE: { + int type = rawCursor.getInt(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE)); + String customLabel = rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL)); + + row.put("label", ContactsContract.CommonDataKinds.Phone.getTypeLabel(activity.getResources(), type, customLabel)); + row.put("phone", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))); + + phones.add(row); + break; + } + case ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE: { + int type = rawCursor.getInt(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.TYPE)); + String customLabel = rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.LABEL)); + + row.put("label", ContactsContract.CommonDataKinds.StructuredPostal.getTypeLabel(activity.getResources(), type, customLabel)); + row.put("street", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.STREET))); + row.put("pobox", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.POBOX))); + row.put("neighborhood", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD))); + row.put("city", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.CITY))); + row.put("region", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.REGION))); + row.put("postcode", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE))); + row.put("country", rawCursor.getString(rawCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY))); + + addresses.add(row); + break; + } + } + } while (rawCursor.moveToNext()); + } + } finally { + if (rawCursor != null) { + rawCursor.close(); + } + } - int phoneType = cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE)); - String customLabel = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL)); - String label = (String) ContactsContract.CommonDataKinds.Email.getTypeLabel(activity.getResources(), phoneType, customLabel); - String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); - String fullName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + String fullName = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); - HashMap phoneNumber = new HashMap<>(); - phoneNumber.put("number", number); - phoneNumber.put("label", label); + HashMap contact = new HashMap<>(); + contact.put("fullName", fullName); - HashMap contact = new HashMap<>(); - contact.put("fullName", fullName); - contact.put("phoneNumber", phoneNumber); + contact.put("emails", emails); + contact.put("phones", phones); + contact.put("addresses", addresses); + contact.put("ims", ims); - pendingResult.success(contact); - pendingResult = null; + pendingResult.success(contact); + pendingResult = null; + } finally { + if (cursor != null) { + cursor.close(); + } + } return true; } + } diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 255ae05..51357ac 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -15,8 +15,7 @@ apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 25 - buildToolsVersion '25.0.3' + compileSdkVersion 27 lintOptions { disable 'InvalidPackage' @@ -26,7 +25,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.yourcompany.contactpickerexample" minSdkVersion 16 - targetSdkVersion 25 + targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -46,7 +45,7 @@ flutter { } dependencies { - androidTestCompile 'com.android.support:support-annotations:25.4.0' - androidTestCompile 'com.android.support.test:runner:0.5' - androidTestCompile 'com.android.support.test:rules:0.5' + androidTestImplementation 'com.android.support:support-annotations:27.1.1' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test:rules:1.0.2' } diff --git a/example/android/app/src/main/java/net/goderbauer/flutter/contactpickerexample/MainActivity.java b/example/android/app/src/main/java/net/goderbauer/flutter/contactpickerexample/MainActivity.java index 8c2b89e..7d87938 100644 --- a/example/android/app/src/main/java/net/goderbauer/flutter/contactpickerexample/MainActivity.java +++ b/example/android/app/src/main/java/net/goderbauer/flutter/contactpickerexample/MainActivity.java @@ -13,6 +13,9 @@ public class MainActivity extends FlutterActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + + GeneratedPluginRegistrant.registerWith(this); } } diff --git a/example/android/build.gradle b/example/android/build.gradle index 77cbd09..3478eb9 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -4,10 +4,11 @@ buildscript { maven { url "https://maven.google.com" } + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.1.3' } } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 45e7f14..ea854bc 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jun 23 08:50:38 CEST 2017 +#Tue Jun 26 18:52:58 CST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/example/ios/.gitignore b/example/ios/.gitignore index 1e1aafd..79cc4da 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -12,6 +12,8 @@ build/ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m +.generated/ + *.pbxuser *.mode1v3 *.mode2v3 @@ -40,3 +42,4 @@ Icon? /ServiceDefinitions.json Pods/ +.symlinks/ diff --git a/example/ios/Podfile b/example/ios/Podfile index 90b5f65..09d050b 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,36 +1,65 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +platform :ios, '10.0' -if ENV['FLUTTER_FRAMEWORK_DIR'] == nil - abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework') +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) { |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + pods_ary.push({:name => podname, :path => podpath}); + else + puts "Invalid plugin specification: #{line}" + end + } + return pods_ary end target 'Runner' do - # Pods for Runner - - # Flutter Pods - pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] - - if File.exists? '../.flutter-plugins' - flutter_root = File.expand_path('..') - File.foreach('../.flutter-plugins') { |line| - plugin = line.split(pattern='=') - if plugin.length == 2 - name = plugin[0].strip() - path = plugin[1].strip() - resolved_path = File.expand_path("#{path}/ios", flutter_root) - pod name, :path => resolved_path - else - puts "Invalid plugin specification: #{line}" - end + use_frameworks! + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + + # Flutter Pods + generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') + if generated_xcode_build_settings.empty? + puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." + end + generated_xcode_build_settings.map { |p| + if p[:name] == 'FLUTTER_FRAMEWORK_DIR' + symlink = File.join('.symlinks', 'flutter') + File.symlink(File.dirname(p[:path]), symlink) + pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + end + } + + # Plugin Pods + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.map { |p| + symlink = File.join('.symlinks', 'plugins', p[:name]) + File.symlink(p[:path], symlink) + pod p[:name], :path => File.join(symlink, 'ios') } - end end post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['ENABLE_BITCODE'] = 'NO' + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end end - end end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 7343227..c02c8a5 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,21 +2,27 @@ PODS: - contact_picker (0.0.1): - Flutter - Flutter (1.0.0) + - simple_permissions (0.0.1): + - Flutter DEPENDENCIES: - - contact_picker (from `/Users/goderbauer/dev/contact_picker/ios`) - - Flutter (from `/Users/goderbauer/dev/flutter/bin/cache/artifacts/engine/ios`) + - contact_picker (from `.symlinks/plugins/contact_picker/ios`) + - Flutter (from `.symlinks/flutter/ios`) + - simple_permissions (from `.symlinks/plugins/simple_permissions/ios`) EXTERNAL SOURCES: contact_picker: - :path: /Users/goderbauer/dev/contact_picker/ios + :path: ".symlinks/plugins/contact_picker/ios" Flutter: - :path: /Users/goderbauer/dev/flutter/bin/cache/artifacts/engine/ios + :path: ".symlinks/flutter/ios" + simple_permissions: + :path: ".symlinks/plugins/simple_permissions/ios" SPEC CHECKSUMS: contact_picker: e5ff7027200ed490ac2b58a2338f81f79b05fe4f Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 + simple_permissions: 7b655133eecb5dd18d5f0326e847c46f8d440576 -PODFILE CHECKSUM: 351e02e34b831289961ec3558a535cbd2c4965d2 +PODFILE CHECKSUM: c19a5e24ea5505fc3a2a4f3c1e1112339b1f4411 -COCOAPODS: 1.4.0 +COCOAPODS: 1.5.3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 624fcd3..cf9ca56 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,16 +9,13 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; - 32CEFA13E5AC0517B32DBBAA /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 900C007205E9217FCD6268B0 /* libPods-Runner.a */; }; + 3473BB5320E3148E00DD277B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3473BB5220E3148E00DD277B /* AppDelegate.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3E030D7EE9B36E07981E5EFF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F3FA8D32C9EDBC4BB799861 /* Pods_Runner.framework */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -43,17 +40,16 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; + 345BF2D220E321940073654E /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.objj.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 3473BB5220E3148E00DD277B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 5F3FA8D32C9EDBC4BB799861 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 900C007205E9217FCD6268B0 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -67,7 +63,7 @@ files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, - 32CEFA13E5AC0517B32DBBAA /* libPods-Runner.a in Frameworks */, + 3E030D7EE9B36E07981E5EFF /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -117,8 +113,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 3473BB5220E3148E00DD277B /* AppDelegate.swift */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -126,6 +121,7 @@ 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 345BF2D220E321940073654E /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; @@ -133,7 +129,6 @@ 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( - 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; @@ -141,7 +136,7 @@ DBB9FDEE58E2B2B1C3CFB38C /* Frameworks */ = { isa = PBXGroup; children = ( - 900C007205E9217FCD6268B0 /* libPods-Runner.a */, + 5F3FA8D32C9EDBC4BB799861 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -161,7 +156,6 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, B9E7B867FB60F996DE565689 /* [CP] Embed Pods Frameworks */, - 8437CF117FC7F9450F8870D2 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -178,12 +172,13 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0830; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 3GRKCVVJ22; + DevelopmentTeam = 7X4LHQK32Q; + LastSwiftMigration = 0940; }; }; }; @@ -211,10 +206,8 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -255,21 +248,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 8437CF117FC7F9450F8870D2 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -291,11 +269,15 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework", + "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", + "${BUILT_PRODUCTS_DIR}/contact_picker/contact_picker.framework", + "${BUILT_PRODUCTS_DIR}/simple_permissions/simple_permissions.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/contact_picker.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/simple_permissions.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -309,8 +291,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, + 3473BB5320E3148E00DD277B /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -341,20 +322,29 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -377,10 +367,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -389,20 +380,29 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -419,9 +419,10 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -431,9 +432,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ARCHS = arm64; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 3GRKCVVJ22; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = 7X4LHQK32Q; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -447,6 +449,9 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.contactPickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -454,9 +459,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ARCHS = arm64; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 3GRKCVVJ22; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = 7X4LHQK32Q; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -470,6 +476,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.contactPickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1c95807..4bafc45 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ -#import - -@interface AppDelegate : FlutterAppDelegate - -@end diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m deleted file mode 100644 index 6f7eb13..0000000 --- a/example/ios/Runner/AppDelegate.m +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2017 Michael Goderbauer. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "AppDelegate.h" -#include "GeneratedPluginRegistrant.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. - return [super application:application didFinishLaunchingWithOptions:launchOptions]; -} - -@end diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c860f52 --- /dev/null +++ b/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,21 @@ +// +// AppDelegate.swift +// Runner +// +// Created by Sebastian Roth on 6/27/18. +// Copyright © 2018 The Chromium Authors. All rights reserved. +// + +import UIKit +import Flutter + +@UIApplicationMain +class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index e119e56..5a6ec2f 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -22,6 +22,8 @@ 1 LSRequiresIPhoneOS + NSContactsUsageDescription + Plugin Demonstration UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/Runner/main.m b/example/ios/Runner/main.m deleted file mode 100644 index d45b5cb..0000000 --- a/example/ios/Runner/main.m +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2017 Michael Goderbauer. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import -#import -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/example/lib/main.dart b/example/lib/main.dart index 1f635ef..bb504ea 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:contact_picker/contact_picker.dart'; +import 'package:simple_permissions/simple_permissions.dart'; + void main() { runApp(new MyApp()); } @@ -18,20 +20,61 @@ class _MyAppState extends State { final ContactPicker _contactPicker = new ContactPicker(); Contact _contact; + bool _hasPermissions = false; + + @override + void initState() { + super.initState(); + + initPermissions(); + } + + void initPermissions() async { + final granted = + await SimplePermissions.checkPermission(Permission.ReadContacts); + setState(() { + _hasPermissions = granted; + }); + } + + void _requestPermissions() async { + final granted = + await SimplePermissions.requestPermission(Permission.ReadContacts); + setState(() { + _hasPermissions = granted; + }); + } + @override Widget build(BuildContext context) { return new MaterialApp( home: new Scaffold( appBar: new AppBar( - title: new Text('Plugin example app'), + title: const Text('Plugin example app'), ), body: new Center( child: new Column( mainAxisSize: MainAxisSize.min, children: [ + !_hasPermissions + ? Padding( + padding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 32.0), + child: MaterialButton( + onPressed: _requestPermissions, + color: Colors.amber, + child: Text( + "Request permissions", + style: TextStyle(color: Colors.red), + ), + ), + ) + : Container(), new MaterialButton( color: Colors.blue, - child: new Text("CLICK ME"), + child: const Text( + "CLICK ME", + style: TextStyle(color: Colors.white), + ), onPressed: () async { Contact contact = await _contactPicker.selectContact(); setState(() { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f92bcc3..1b01cb1 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -2,6 +2,9 @@ name: contact_picker_example description: Demonstrates how to use the contact_picker plugin. dependencies: + # simple_permissions: ^0.1.5 + simple_permissions: + path: ../../ened.flutter_simple_permissions flutter: sdk: flutter diff --git a/ios/Classes/ContactPickerPlugin.m b/ios/Classes/ContactPickerPlugin.m index 6da8c9f..a3f2aaf 100644 --- a/ios/Classes/ContactPickerPlugin.m +++ b/ios/Classes/ContactPickerPlugin.m @@ -9,55 +9,102 @@ @interface ContactPickerPlugin () @end @implementation ContactPickerPlugin { - FlutterResult _result; + FlutterResult _result; } + (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"contact_picker" - binaryMessenger:[registrar messenger]]; - ContactPickerPlugin *instance = [[ContactPickerPlugin alloc] init]; - [registrar addMethodCallDelegate:instance channel:channel]; + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"contact_picker" + binaryMessenger:[registrar messenger]]; + ContactPickerPlugin *instance = [[ContactPickerPlugin alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"selectContact" isEqualToString:call.method]) { - if (_result) { - _result([FlutterError errorWithCode:@"multiple_requests" - message:@"Cancelled by a second request." - details:nil]); - _result = nil; + if ([@"selectContact" isEqualToString:call.method]) { + if (_result) { + _result([FlutterError errorWithCode:@"multiple_requests" + message:@"Cancelled by a second request." + details:nil]); + _result = nil; + } + _result = result; + + CNContactPickerViewController *contactPicker = [[CNContactPickerViewController alloc] init]; + contactPicker.delegate = self; +// contactPicker.displayedPropertyKeys = @[ CNContactPhoneNumbersKey ]; + + UIViewController *viewController = + [UIApplication sharedApplication].delegate.window.rootViewController; + [viewController presentViewController:contactPicker animated:YES completion:nil]; + } else { + result(FlutterMethodNotImplemented); } - _result = result; +} - CNContactPickerViewController *contactPicker = [[CNContactPickerViewController alloc] init]; - contactPicker.delegate = self; - contactPicker.displayedPropertyKeys = @[ CNContactPhoneNumbersKey ]; +- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact { + NSString *fullName = [CNContactFormatter stringFromContact:contact + style:CNContactFormatterStyleFullName]; + + NSMutableArray *emails = [NSMutableArray array]; + NSMutableArray *phones = [NSMutableArray array]; + NSMutableArray *addresses = [NSMutableArray array]; + NSMutableArray *ims = [NSMutableArray array]; + + for (CNLabeledValue * email in contact.emailAddresses) { + [emails addObject:@{ @"label": [CNLabeledValue localizedStringForLabel: email.label], + @"email": email.value }]; + } + + for (CNLabeledValue * phone in contact.phoneNumbers) { + [phones addObject:@{ @"label": [CNLabeledValue localizedStringForLabel: phone.label], + @"phone": phone.value.stringValue }]; + } + + for (CNLabeledValue * im in contact.instantMessageAddresses) { + [ims addObject:@{ @"label": [CNLabeledValue localizedStringForLabel: im.label], + @"im": im.value.username, + @"protocol": im.value.service + }]; + } + for (CNLabeledValue * address in contact.postalAddresses) { + if (@available(iOS 10.3, *)) { + [addresses addObject:@{ @"label": [CNLabeledValue localizedStringForLabel: address.label], + @"street": address.value.street, + @"neighborhood": address.value.subLocality, + @"city": address.value.city, + @"region": address.value.subAdministrativeArea, + @"state": address.value.state, + @"postcode": address.value.postalCode, + @"country": address.value.country + }]; + } else { + [addresses addObject:@{ @"label": [CNLabeledValue localizedStringForLabel: address.label], + @"street": address.value.street, + @"city": address.value.city, + @"state": address.value.state, + @"postcode": address.value.postalCode, + @"country": address.value.country + }]; + } + } + + if (!fullName) { + fullName = [emails.firstObject valueForKeyPath:@"email"]; + } + if (!fullName) { + fullName = [phones.firstObject valueForKeyPath:@"phone"]; + } - UIViewController *viewController = - [UIApplication sharedApplication].delegate.window.rootViewController; - [viewController presentViewController:contactPicker animated:YES completion:nil]; - } else { - result(FlutterMethodNotImplemented); - } -} + _result(@{ @"fullName": fullName, @"emails": emails, @"phones": phones, @"addresses": addresses, @"ims": ims }); + _result = nil; -- (void)contactPicker:(CNContactPickerViewController *)picker - didSelectContactProperty:(CNContactProperty *)contactProperty { - NSString *fullName = [CNContactFormatter stringFromContact:contactProperty.contact - style:CNContactFormatterStyleFullName]; - NSDictionary *phoneNumber = [NSDictionary - dictionaryWithObjectsAndKeys:[contactProperty.value stringValue], @"number", - [CNLabeledValue localizedStringForLabel:contactProperty.label], - @"label", nil]; - _result([NSDictionary - dictionaryWithObjectsAndKeys:fullName, @"fullName", phoneNumber, @"phoneNumber", nil]); - _result = nil; + NSLog(@"contact: %@", contact); } - (void)contactPickerDidCancel:(CNContactPickerViewController *)picker { - _result(nil); - _result = nil; + _result(nil); + _result = nil; } @end diff --git a/ios/SwiftContactPickerPlugin.swift b/ios/SwiftContactPickerPlugin.swift new file mode 100644 index 0000000..a55a579 --- /dev/null +++ b/ios/SwiftContactPickerPlugin.swift @@ -0,0 +1,8 @@ +// +// SwiftContactPickerPlugin.swift +// contact_picker +// +// Created by Sebastian Roth on 6/27/18. +// + +import Foundation diff --git a/ios/contact_picker-Bridging-Header.h b/ios/contact_picker-Bridging-Header.h new file mode 100644 index 0000000..1b2cb5d --- /dev/null +++ b/ios/contact_picker-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/lib/contact_picker.dart b/lib/contact_picker.dart index 49734e0..0de13c5 100644 --- a/lib/contact_picker.dart +++ b/lib/contact_picker.dart @@ -30,20 +30,41 @@ class ContactPicker { /// Represents a contact selected by the user. class Contact { - Contact({this.fullName, this.phoneNumber}); + Contact( + {this.fullName, + this.phoneNumbers, + this.emails, + this.addresses, + this.ims}); factory Contact.fromMap(Map map) => new Contact( - fullName: map['fullName'], - phoneNumber: new PhoneNumber.fromMap(map['phoneNumber'])); + fullName: map['fullName'], + phoneNumbers: List.from((map['phones']) + .map((dynamic i) => PhoneNumber.fromMap(i))), + emails: List.from( + (map['emails']).map((dynamic i) => Email.fromMap(i))), + addresses: List
.from( + (map['addresses']).map
((dynamic i) => Address.fromMap(i))), + ims: List.from((map['ims']).map((dynamic i) => Im.fromMap(i))), + ); /// The full name of the contact, e.g. "Dr. Daniel Higgens Jr.". final String fullName; - /// The phone number of the contact. - final PhoneNumber phoneNumber; + /// The phone numbers of the contact. + final List phoneNumbers; + + /// The emails of the contact. + final List emails; + + /// The addresses of the contact. + final List
addresses; + + /// The instant messengers of the contact + final List ims; @override - String toString() => '$fullName: $phoneNumber'; + String toString() => '$fullName: $phoneNumbers $emails $addresses $ims'; } /// Represents a phone number selected by the user. @@ -51,7 +72,7 @@ class PhoneNumber { PhoneNumber({this.number, this.label}); factory PhoneNumber.fromMap(Map map) => - new PhoneNumber(number: map['number'], label: map['label']); + new PhoneNumber(number: map['phone'], label: map['label']); /// The formatted phone number, e.g. "+1 (555) 555-5555" final String number; @@ -62,3 +83,86 @@ class PhoneNumber { @override String toString() => '$number ($label)'; } + +/// Represents a phone number selected by the user. +class Address { + Address( + {this.street, + this.pobox, + this.neighborhood, + this.city, + this.region, + this.country, + this.label}); + + factory Address.fromMap(Map map) => new Address( + street: map['street'], + pobox: map['pobox'], + neighborhood: map['neighborhood'], + city: map['city'], + region: map['region'], + country: map['country'], + label: map['label']); + + /// The formatted phone number, e.g. "+1 (555) 555-5555" + final String street; + + /// The formatted phone number, e.g. "+1 (555) 555-5555" + final String pobox; + + /// The formatted phone number, e.g. "+1 (555) 555-5555" + final String neighborhood; + + /// The formatted phone number, e.g. "+1 (555) 555-5555" + final String city; + + /// The formatted phone number, e.g. "+1 (555) 555-5555" + final String region; + + /// The formatted phone number, e.g. "+1 (555) 555-5555" + final String country; + + /// The label associated with the phone number, e.g. "home" or "work". + final String label; + + @override + String toString() => + '$street $pobox $neighborhood $city $region $country ($label)'; +} + +/// Represents a email address +class Email { + Email({this.email, this.label}); + + factory Email.fromMap(Map map) => + new Email(email: map['email'], label: map['label']); + + /// The raw email address + final String email; + + /// The label associated with the email, e.g. "home" or "work". + final String label; + + @override + String toString() => '$email ($label)'; +} + +/// Represents a instant messaging endpoint +class Im { + Im({this.value, this.label, this.protocol}); + + factory Im.fromMap(Map map) => + new Im(value: map['im'], label: map['label'], protocol: map['protocol']); + + /// The IM endpoint + final String value; + + /// The label associated with the endpoint, e.g. "home" or "work". + final String label; + + /// The IM protocol, e.g. Skype, Hangouts ... + final String protocol; + + @override + String toString() => '$value $protocol ($label)'; +}