motion-addressbook 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +0 -1
- data/README.md +2 -3
- data/Rakefile +16 -4
- data/lib/motion-addressbook/version.rb +1 -1
- data/lib/motion-addressbook.rb +22 -10
- data/motion/address_book/{addr_book.rb → ios/addr_book.rb} +0 -0
- data/motion/address_book/{group.rb → ios/group.rb} +0 -0
- data/motion/address_book/{multi_value.rb → ios/multi_value.rb} +0 -0
- data/motion/address_book/{multi_valued.rb → ios/multi_valued.rb} +0 -0
- data/motion/address_book/{person.rb → ios/person.rb} +8 -0
- data/motion/address_book/{picker.rb → ios/picker.rb} +0 -0
- data/motion/address_book/{source.rb → ios/source.rb} +0 -0
- data/motion/address_book/osx/addr_book.rb +67 -0
- data/motion/address_book/osx/group.rb +122 -0
- data/motion/address_book/osx/multi_valued.rb +166 -0
- data/motion/address_book/osx/person.rb +350 -0
- data/motion/address_book.rb +13 -5
- data/motion-addressbook.gemspec +2 -2
- data/spec/{address_book → ios/address_book}/group_spec.rb +0 -0
- data/spec/{address_book → ios/address_book}/multi_valued_spec.rb +0 -0
- data/spec/{address_book → ios/address_book}/person_spec.rb +33 -154
- data/spec/{address_book → ios/address_book}/picker_spec.rb +0 -0
- data/spec/{helpers → ios/helpers}/bacon_matchers.rb +0 -0
- data/spec/{helpers → ios/helpers}/hacks.rb +0 -0
- data/spec/{helpers → ios/helpers}/person_helpers.rb +0 -0
- data/spec/osx/address_book/person_spec.rb +174 -0
- data/spec/osx/helpers/bacon_matchers.rb +11 -0
- data/spec/osx/helpers/hacks.rb +6 -0
- data/spec/osx/helpers/person_helpers.rb +12 -0
- metadata +39 -29
- data/spec/address_book/multi_value_spec.rb +0 -125
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -2,9 +2,8 @@
|
|
2
2
|
|
3
3
|
A RubyMotion wrapper around the iOS Address Book framework for RubyMotion apps.
|
4
4
|
|
5
|
-
Apple's
|
6
|
-
|
7
|
-
[![Gem Version](https://badge.fury.io/rb/motion-addressbook.png)](http://badge.fury.io/rb/motion-addressbook)
|
5
|
+
Apple's Address Book Programming Guide for [iOS](http://developer.apple.com/library/ios/#DOCUMENTATION/ContactData/Conceptual/AddressBookProgrammingGuideforiPhone/Introduction.html)
|
6
|
+
or for [OSX](https://developer.apple.com/library/mac/#documentation/userexperience/Conceptual/AddressBook/AddressBook.html#//apple_ref/doc/uid/10000117i)
|
8
7
|
|
9
8
|
## Installation
|
10
9
|
|
data/Rakefile
CHANGED
@@ -1,13 +1,25 @@
|
|
1
|
-
#!/usr/bin/env rake
|
2
|
-
$:.unshift("/Library/RubyMotion/lib")
|
3
|
-
require 'motion/project/template/ios'
|
4
1
|
require "bundler/gem_tasks"
|
2
|
+
$:.unshift("/Library/RubyMotion/lib")
|
3
|
+
if ENV['osx']
|
4
|
+
require 'motion/project/template/osx'
|
5
|
+
else
|
6
|
+
require 'motion/project/template/ios'
|
7
|
+
end
|
5
8
|
Bundler.setup
|
6
9
|
Bundler.require
|
7
10
|
|
8
|
-
|
11
|
+
unless ENV['osx']
|
12
|
+
# iOS needs an AppDelegate for REPL to launch; steal one from BW
|
13
|
+
require 'bubble-wrap/test'
|
14
|
+
end
|
9
15
|
|
10
16
|
Motion::Project::App.setup do |app|
|
11
17
|
# Use `rake config' to see complete project settings.
|
12
18
|
app.name = 'AddressBook'
|
19
|
+
|
20
|
+
if Motion::Project::App.osx?
|
21
|
+
app.specs_dir = "./spec/osx"
|
22
|
+
else
|
23
|
+
app.specs_dir = "./spec/ios"
|
24
|
+
end
|
13
25
|
end
|
data/lib/motion-addressbook.rb
CHANGED
@@ -3,17 +3,29 @@ require "motion-addressbook/version"
|
|
3
3
|
BubbleWrap.require 'motion/address_book.rb' do
|
4
4
|
file('motion/address_book.rb').uses_framework('AddressBook')
|
5
5
|
end
|
6
|
-
BW.require 'motion/address_book/multi_value.rb'
|
7
|
-
BW.require 'motion/address_book/addr_book.rb'
|
8
|
-
BW.require 'motion/address_book/person.rb'
|
9
|
-
BW.require 'motion/address_book/group.rb'
|
10
|
-
BW.require 'motion/address_book/multi_valued.rb'
|
11
|
-
BW.require 'motion/address_book/source.rb'
|
12
6
|
|
13
|
-
|
14
|
-
|
7
|
+
BubbleWrap.require_ios do
|
8
|
+
# BW.require 'motion/address_book/multi_value.rb'
|
9
|
+
BW.require 'motion/address_book/ios/addr_book.rb'
|
10
|
+
BW.require 'motion/address_book/ios/person.rb'
|
11
|
+
BW.require 'motion/address_book/ios/group.rb'
|
12
|
+
BW.require 'motion/address_book/ios/multi_valued.rb'
|
13
|
+
BW.require 'motion/address_book/ios/source.rb'
|
14
|
+
|
15
|
+
# This is an iOS-specific RubyMotion bug workaround.
|
16
|
+
Motion::Project::App.setup do |app|
|
17
|
+
app.vendor_project(File.expand_path(File.join(File.dirname(__FILE__), '../abhack')), :static)
|
18
|
+
end
|
19
|
+
|
20
|
+
BW.require 'motion/address_book/ios/picker.rb' do
|
21
|
+
file('motion/address_book/ios/picker.rb').uses_framework('AddressBookUI')
|
22
|
+
end
|
15
23
|
end
|
16
24
|
|
17
|
-
|
18
|
-
|
25
|
+
BubbleWrap.require_osx do
|
26
|
+
BW.require 'motion/address_book/osx/addr_book.rb'
|
27
|
+
BW.require 'motion/address_book/osx/person.rb'
|
28
|
+
BW.require 'motion/address_book/osx/group.rb'
|
29
|
+
BW.require 'motion/address_book/osx/multi_valued.rb'
|
30
|
+
BW.require 'motion/address_book/osx/source.rb'
|
19
31
|
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -77,6 +77,7 @@ module AddressBook
|
|
77
77
|
:first_name => KABPersonFirstNameProperty,
|
78
78
|
:middle_name => KABPersonMiddleNameProperty,
|
79
79
|
:last_name => KABPersonLastNameProperty,
|
80
|
+
:prefix => KABPersonPrefixProperty,
|
80
81
|
:suffix => KABPersonSuffixProperty,
|
81
82
|
:nickname => KABPersonNicknameProperty,
|
82
83
|
:job_title => KABPersonJobTitleProperty,
|
@@ -86,6 +87,7 @@ module AddressBook
|
|
86
87
|
:note => KABPersonNoteProperty
|
87
88
|
}
|
88
89
|
end
|
90
|
+
|
89
91
|
def attribute_map
|
90
92
|
self.class.attribute_map
|
91
93
|
end
|
@@ -344,6 +346,11 @@ module AddressBook
|
|
344
346
|
ABPersonCreateVCardRepresentationWithPeople(ab_persons)
|
345
347
|
end
|
346
348
|
|
349
|
+
def to_s
|
350
|
+
"#<#{self.class}:#{uid}: #{attributes}>"
|
351
|
+
end
|
352
|
+
alias :inspect :to_s
|
353
|
+
|
347
354
|
private
|
348
355
|
|
349
356
|
def self.single_value_property_map
|
@@ -351,6 +358,7 @@ module AddressBook
|
|
351
358
|
KABPersonFirstNameProperty => :first_name,
|
352
359
|
KABPersonLastNameProperty => :last_name,
|
353
360
|
KABPersonMiddleNameProperty => :middle_name,
|
361
|
+
KABPersonPrefixProperty => :prefix,
|
354
362
|
KABPersonSuffixProperty => :suffix,
|
355
363
|
KABPersonNicknameProperty => :nickname,
|
356
364
|
KABPersonJobTitleProperty => :job_title,
|
File without changes
|
File without changes
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module AddressBook
|
2
|
+
class AddrBook
|
3
|
+
attr_reader :ab
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@ab = ABAddressBook.addressBook
|
7
|
+
end
|
8
|
+
def people(opts = {})
|
9
|
+
if opts[:local]
|
10
|
+
people.select {|p| p.local?}
|
11
|
+
else
|
12
|
+
ab.people.map do |ab_person|
|
13
|
+
AddressBook::Person.new(ab_person, :address_book => ab)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
def count
|
18
|
+
people.count
|
19
|
+
end
|
20
|
+
def new_person(attributes)
|
21
|
+
Person.new(attributes, :address_book => @ab)
|
22
|
+
end
|
23
|
+
def create_person(attributes)
|
24
|
+
p = Person.new(attributes, :address_book => @ab)
|
25
|
+
p.save
|
26
|
+
p
|
27
|
+
end
|
28
|
+
def person(id)
|
29
|
+
if ab_person = ab.recordForUniqueId(id)
|
30
|
+
Person.new(ab_person, :address_book => ab)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
def changedSince(timestamp)
|
34
|
+
people.select {|p| p.modification_date > timestamp}
|
35
|
+
end
|
36
|
+
|
37
|
+
# get logged-in user's record
|
38
|
+
def me
|
39
|
+
if this_user = ab.me
|
40
|
+
Person.new(this_user, :address_book => ab)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def groups
|
45
|
+
ab.groups.map do |ab_group|
|
46
|
+
AddressBook::Group.new(:ab_group => ab_group, :address_book => @ab)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
def new_group(attributes)
|
50
|
+
AddressBook::Group.new(:attributes => attributes, :address_book => @ab)
|
51
|
+
end
|
52
|
+
def group(id)
|
53
|
+
if ab_group = ab.recordForUniqueId(id)
|
54
|
+
Group.new(:ab_group => ab_group, :address_book => ab)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def notify_changes(callback, context)
|
59
|
+
ABAddressBookRegisterExternalChangeCallback(ab, callback, context)
|
60
|
+
end
|
61
|
+
|
62
|
+
# def sources
|
63
|
+
# # ABAddressBookCopyArrayOfAllSources(ab).map {|s| ABRecordCopyValue(s, KABSourceTypeProperty)}
|
64
|
+
# ABAddressBookCopyArrayOfAllSources(ab).map {|s| Source.new(s)}
|
65
|
+
# end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# Wrapper for OSX ABGroup
|
2
|
+
#
|
3
|
+
# * groups are saved to the database immediately upon new()
|
4
|
+
# * members are added with <<
|
5
|
+
#
|
6
|
+
module AddressBook
|
7
|
+
class Group
|
8
|
+
attr_reader :attributes, :error
|
9
|
+
|
10
|
+
def initialize(opts)
|
11
|
+
@address_book = opts[:address_book]
|
12
|
+
if opts[:ab_group]
|
13
|
+
# import existing
|
14
|
+
@ab_group = opts[:ab_group]
|
15
|
+
@attributes = nil
|
16
|
+
else
|
17
|
+
# create new
|
18
|
+
@ab_group = nil
|
19
|
+
@attributes = opts[:attributes]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def address_book
|
24
|
+
@address_book ||= AddressBook.address_book
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
address_book.addRecord(ab_group)
|
29
|
+
address_book.save
|
30
|
+
@attributes = nil
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def exists?
|
35
|
+
address_book.recordForUniqueId(uid)
|
36
|
+
end
|
37
|
+
def new_record?
|
38
|
+
!exists?
|
39
|
+
end
|
40
|
+
alias :new? :new_record?
|
41
|
+
|
42
|
+
def delete!
|
43
|
+
unless new?
|
44
|
+
address_book.removeRecord(ab_group)
|
45
|
+
address_book.save
|
46
|
+
@ab_group = nil
|
47
|
+
self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def ab_group
|
52
|
+
@ab_group || convert_dict_to_ab
|
53
|
+
end
|
54
|
+
alias :ab_record :ab_group
|
55
|
+
|
56
|
+
def get_field(field)
|
57
|
+
ab_group.valueForProperty(field)
|
58
|
+
end
|
59
|
+
|
60
|
+
def uid
|
61
|
+
get_field(KABUIDProperty)
|
62
|
+
end
|
63
|
+
|
64
|
+
def name
|
65
|
+
get_field(KABGroupNameProperty)
|
66
|
+
end
|
67
|
+
|
68
|
+
def size
|
69
|
+
members.count
|
70
|
+
end
|
71
|
+
|
72
|
+
def members
|
73
|
+
people + subgroups
|
74
|
+
end
|
75
|
+
def people
|
76
|
+
ab_group.members.map do |ab_person|
|
77
|
+
AddressBook::Person.new(ab_person, :address_book => address_book)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
def subgroups
|
81
|
+
ab_group.subgroups.map do |subgroup|
|
82
|
+
AddressBook::Group.new(:ab_group => subgroup, :address_book => address_book)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def <<(person_or_group)
|
87
|
+
raise ArgumentError, "Must save member before adding to group" if person_or_group.new?
|
88
|
+
ABGroupAddMember(ab_group, person_or_group.ab_record, error)
|
89
|
+
end
|
90
|
+
|
91
|
+
def local?
|
92
|
+
!ab_group.isReadOnly
|
93
|
+
end
|
94
|
+
|
95
|
+
def apple_uuid
|
96
|
+
get_field('com.apple.uuid')
|
97
|
+
end
|
98
|
+
# regular groups have a value in the internal "com.apple.uuid" property
|
99
|
+
# groups with nil here appear to include
|
100
|
+
# * all-local-contacts source (group name is "card")
|
101
|
+
# * Facebook source (group name is "addressbook")
|
102
|
+
# I suspect that Exchange and DAV sources will show up here too.
|
103
|
+
def special?
|
104
|
+
apple_uuid.nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def convert_dict_to_ab
|
110
|
+
@ab_group = ABGroup.alloc.initWithAddressBook(address_book)
|
111
|
+
|
112
|
+
# groups only have a single regular attribute (name)
|
113
|
+
if v = @attributes[:name]
|
114
|
+
ab_group.setValue(v, forProperty:KABGroupNameProperty)
|
115
|
+
end
|
116
|
+
|
117
|
+
save
|
118
|
+
|
119
|
+
@ab_group
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module AddressBook
|
2
|
+
class MultiValued
|
3
|
+
attr_reader :mv_type
|
4
|
+
|
5
|
+
def initialize(opts)
|
6
|
+
unless opts.one?
|
7
|
+
raise ArgumentError, "MultiValued requires :attributes *or* :ab_multi_value argument"
|
8
|
+
end
|
9
|
+
|
10
|
+
if opts[:ab_multi_value]
|
11
|
+
@ab_multi_value = opts[:ab_multi_value] #ABMultiValueCreateMutableCopy(opts[:ab_multi_value])
|
12
|
+
else
|
13
|
+
@attributes = opts[:attributes]
|
14
|
+
raise ArgumentError, "Empty multi-value objects are not allowed" if @attributes.empty?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def count
|
19
|
+
ab_multi_value.count
|
20
|
+
end
|
21
|
+
alias :size :count
|
22
|
+
|
23
|
+
def attributes
|
24
|
+
@attributes ||= convert_multi_value_into_dictionary
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert_multi_value_into_dictionary
|
28
|
+
count.times.map do |i|
|
29
|
+
data = ab_record_to_dict(i)
|
30
|
+
label = @ab_multi_value.labelAtIndex(i)
|
31
|
+
if label != ''
|
32
|
+
label_val = ABPerson.ABCopyLocalizedPropertyOrLabel(label)
|
33
|
+
data[:label] = label_val
|
34
|
+
end
|
35
|
+
data
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def ab_multi_value
|
40
|
+
@ab_multi_value ||= convert_dictionary_into_multi_value
|
41
|
+
end
|
42
|
+
|
43
|
+
# Attempt to convert the provided label to the OSX-standard global constant.
|
44
|
+
# If there's no match, then use the string as provided.
|
45
|
+
# nil values are not allowed on OSX, "" seems to be the stand-in for undefined.
|
46
|
+
def localized_label(str)
|
47
|
+
LabelMap[str] || str || ''
|
48
|
+
end
|
49
|
+
|
50
|
+
def convert_dictionary_into_multi_value
|
51
|
+
@mv_type = multi_value_property_type
|
52
|
+
mv = ABMutableMultiValue.new
|
53
|
+
|
54
|
+
case mv_type
|
55
|
+
when KABMultiStringProperty
|
56
|
+
@attributes.each do |rec|
|
57
|
+
mv.addValue(rec[:value], withLabel: localized_label(rec[:label]))
|
58
|
+
end
|
59
|
+
when KABMultiDateProperty
|
60
|
+
@attributes.each do |rec|
|
61
|
+
mv.addValue(rec[:date], withLabel: localized_label(rec[:label]))
|
62
|
+
end
|
63
|
+
else # KABMultiDictionaryProperty
|
64
|
+
@attributes.each do |rec|
|
65
|
+
if value = dict_to_ab_record(rec)
|
66
|
+
mv.addValue(value, withLabel: localized_label(rec[:label]))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
mv
|
72
|
+
end
|
73
|
+
|
74
|
+
def multi_value_property_type
|
75
|
+
if @ab_multi_value
|
76
|
+
@ab_multi_value.propertyType
|
77
|
+
else
|
78
|
+
if attributes.find {|rec| rec[:value]}
|
79
|
+
KABMultiStringProperty
|
80
|
+
elsif attributes.find {|rec| rec[:date]}
|
81
|
+
KABMultiDateProperty
|
82
|
+
else
|
83
|
+
KABMultiDictionaryProperty
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# these are for mapping fields in a kABMultiDictionaryPropertyType record
|
89
|
+
# to keys in a standard hash (NSDictionary)
|
90
|
+
PropertyMap = {
|
91
|
+
KABAddressStreetKey => :street,
|
92
|
+
KABAddressCityKey => :city,
|
93
|
+
KABAddressStateKey => :state,
|
94
|
+
KABAddressZIPKey => :postalcode,
|
95
|
+
KABAddressCountryKey => :country,
|
96
|
+
KABAddressCountryCodeKey => :country_code,
|
97
|
+
|
98
|
+
KABSocialProfileURLKey => :url,
|
99
|
+
KABSocialProfileServiceKey => :service,
|
100
|
+
KABSocialProfileUsernameKey => :username,
|
101
|
+
KABSocialProfileUserIdentifierKey => :userid,
|
102
|
+
|
103
|
+
# these keys are identical to the SocialProfile keys above
|
104
|
+
KABInstantMessageServiceKey => :service,
|
105
|
+
KABInstantMessageUsernameKey => :username
|
106
|
+
}
|
107
|
+
|
108
|
+
LabelMap = {
|
109
|
+
"mobile" => KABPhoneMobileLabel ,
|
110
|
+
"iphone" => KABPhoneiPhoneLabel ,
|
111
|
+
"main" => KABPhoneMainLabel ,
|
112
|
+
"home_fax" => KABPhoneHomeFAXLabel,
|
113
|
+
"work_fax" => KABPhoneWorkFAXLabel,
|
114
|
+
"pager" => KABPhonePagerLabel ,
|
115
|
+
"work" => KABWorkLabel ,
|
116
|
+
"home" => KABHomeLabel ,
|
117
|
+
"other" => KABOtherLabel ,
|
118
|
+
"home page"=> KABHomePageLabel,
|
119
|
+
"anniversary"=> KABAnniversaryLabel
|
120
|
+
}
|
121
|
+
|
122
|
+
def dict_to_ab_record(h)
|
123
|
+
h = PropertyMap.each_with_object({}) do |(ab_key, attr_key), ab_record|
|
124
|
+
ab_record[ab_key] = h[attr_key] if h[attr_key]
|
125
|
+
end
|
126
|
+
h.any? ? h : nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def ab_record_to_dict(i)
|
130
|
+
case multi_value_property_type
|
131
|
+
when KABMultiStringProperty
|
132
|
+
{:value => @ab_multi_value.valueAtIndex(i)}
|
133
|
+
when KABMultiDateProperty
|
134
|
+
{:date => @ab_multi_value.valueAtIndex(i)}
|
135
|
+
when KABMultiDictionaryProperty
|
136
|
+
ab_record = @ab_multi_value.valueAtIndex(i)
|
137
|
+
PropertyMap.each_with_object({}) do |(ab_key, attr_key), dict|
|
138
|
+
dict[attr_key] = ab_record[ab_key] if ab_record[ab_key]
|
139
|
+
end
|
140
|
+
else
|
141
|
+
raise TypeError, "Unknown MultiValue property type #{multi_value_property_type}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def <<(rec)
|
146
|
+
case multi_value_property_type
|
147
|
+
when KABMultiStringProperty
|
148
|
+
ABMultiValueAddValueAndLabel(ab_multi_value, rec[:value], localized_label(rec[:label]), nil)
|
149
|
+
when KABMultiDateTimeProperty
|
150
|
+
ABMultiValueAddValueAndLabel(ab_multi_value, rec[:date], localized_label(rec[:label]), nil)
|
151
|
+
when KABMultiDictionaryProperty
|
152
|
+
ABMultiValueAddValueAndLabel(ab_multi_value, dict_to_ab_record(rec), localized_label(rec[:label]), nil)
|
153
|
+
else
|
154
|
+
raise TypeError, "Unknown MultiValue property type #{multi_value_property_type}"
|
155
|
+
end
|
156
|
+
|
157
|
+
@attributes = convert_multi_value_into_dictionary
|
158
|
+
end
|
159
|
+
|
160
|
+
def first_for(label)
|
161
|
+
if rec = attributes.find {|r| r[:label] == label.to_s}
|
162
|
+
rec[:value] ? rec[:value] : rec
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|