motion-addressbook 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
@@ -0,0 +1,350 @@
|
|
1
|
+
module AddressBook
|
2
|
+
class Person
|
3
|
+
attr_reader :error
|
4
|
+
|
5
|
+
def initialize(target, opts = {})
|
6
|
+
@address_book = opts[:address_book]
|
7
|
+
if target.respond_to?(:fetch)
|
8
|
+
# data for a new Person, to be saved to the Address Book
|
9
|
+
@ab_person = nil
|
10
|
+
@attributes = target
|
11
|
+
else
|
12
|
+
# existing Person, to be retrieved from the OSX Address Book
|
13
|
+
@ab_person = target
|
14
|
+
@attributes = nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def save
|
19
|
+
address_book.addRecord(ab_person)
|
20
|
+
address_book.save
|
21
|
+
@attributes = nil # force refresh
|
22
|
+
uid
|
23
|
+
end
|
24
|
+
|
25
|
+
def attributes
|
26
|
+
@attributes || import_ab_person
|
27
|
+
end
|
28
|
+
|
29
|
+
def ab_person
|
30
|
+
@ab_person ||= initialize_ab_person
|
31
|
+
end
|
32
|
+
alias :ab_record :ab_person
|
33
|
+
|
34
|
+
def uid
|
35
|
+
get_field(KABUIDProperty)
|
36
|
+
end
|
37
|
+
|
38
|
+
# this is NOT the same as the uid
|
39
|
+
# this may be assigned when iCloud syncing is enabled
|
40
|
+
def apple_uuid
|
41
|
+
get_field('com.apple.uuid')
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(name, *args)
|
45
|
+
if property = ReverseSingleValuePropertyMap[name]
|
46
|
+
get_field(property)
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
# def self.method_missing(name, *args)
|
52
|
+
# if attribute_name = all_finder?(name)
|
53
|
+
# find_all_by(attribute_name, args.first)
|
54
|
+
# elsif attribute_name = first_finder?(name)
|
55
|
+
# find_by(attribute_name, args.first)
|
56
|
+
# elsif attribute_name = finder_or_new?(name)
|
57
|
+
# find_or_new_by(attribute_name, args.first)
|
58
|
+
# else
|
59
|
+
# super
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
# def self.is_attribute?(attribute_name)
|
63
|
+
# return false if attribute_name.nil?
|
64
|
+
# attribute_map.include?(attribute_name.to_sym) || [:email, :phone_number].include?( attribute_name.to_sym)
|
65
|
+
# end
|
66
|
+
|
67
|
+
# def getter?(method_name)
|
68
|
+
# if self.class.is_attribute? method_name
|
69
|
+
# method_name
|
70
|
+
# # true
|
71
|
+
# else
|
72
|
+
# nil
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
# def setter?(method_name)
|
76
|
+
# method_name.to_s =~ /^(\w*)=$/
|
77
|
+
# if self.class.is_attribute? $1
|
78
|
+
# $1
|
79
|
+
# else
|
80
|
+
# nil
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
|
84
|
+
def get(attribute_name)
|
85
|
+
# label, attribute = attribute_name.split('_')
|
86
|
+
# self.send("#{attribute}s").first_for(label)
|
87
|
+
attributes[attribute_name.to_sym] ||= get_field(attribute_map[attribute_name])
|
88
|
+
end
|
89
|
+
|
90
|
+
def set(attribute_name, value)
|
91
|
+
set_field(attribute_map[attribute_name.to_sym], value)
|
92
|
+
attributes[attribute_name.to_sym] = value
|
93
|
+
end
|
94
|
+
|
95
|
+
def photo_image
|
96
|
+
UIImage.alloc.initWithData(photo)
|
97
|
+
end
|
98
|
+
|
99
|
+
def photo
|
100
|
+
ab_person.imageData
|
101
|
+
end
|
102
|
+
|
103
|
+
def photo=(photo_data)
|
104
|
+
ab_person.setImageData(photo_data)
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_multi_valued(field)
|
108
|
+
if mv = get_field(field)
|
109
|
+
MultiValued.new(:ab_multi_value => mv)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def phones
|
114
|
+
get_multi_valued(KABPhoneProperty)
|
115
|
+
end
|
116
|
+
|
117
|
+
def phone_values
|
118
|
+
phones.attributes.map {|r| r[:value]}
|
119
|
+
end
|
120
|
+
|
121
|
+
def emails
|
122
|
+
get_multi_valued(KABEmailProperty)
|
123
|
+
end
|
124
|
+
|
125
|
+
def email_values
|
126
|
+
emails.attributes.map {|r| r[:value]}
|
127
|
+
end
|
128
|
+
|
129
|
+
def addresses
|
130
|
+
get_multi_valued(KABAddressProperty)
|
131
|
+
end
|
132
|
+
|
133
|
+
def urls
|
134
|
+
get_multi_valued(KABURLsProperty)
|
135
|
+
end
|
136
|
+
|
137
|
+
def social_profiles
|
138
|
+
get_multi_valued(KABSocialProfileProperty)
|
139
|
+
end
|
140
|
+
|
141
|
+
def im_profiles
|
142
|
+
get_multi_valued(KABInstantMessageProperty)
|
143
|
+
end
|
144
|
+
|
145
|
+
def related_names
|
146
|
+
get_multi_valued(KABRelatedNamesProperty)
|
147
|
+
end
|
148
|
+
|
149
|
+
def dates
|
150
|
+
get_multi_valued(KABDateProperty)
|
151
|
+
end
|
152
|
+
|
153
|
+
def email; email_values.first; end
|
154
|
+
def phone; phone_values.first; end
|
155
|
+
def url; urls.attributes.first[:value]; end
|
156
|
+
def address; addresses.attributes.first; end
|
157
|
+
|
158
|
+
# def find_or_new
|
159
|
+
# if new_record?
|
160
|
+
# new_ab_person
|
161
|
+
# else
|
162
|
+
# existing_record
|
163
|
+
# end
|
164
|
+
# end
|
165
|
+
|
166
|
+
# has this record already been saved to the address book?
|
167
|
+
def exists?
|
168
|
+
uid && ABAddressBook.sharedAddressBook.recordForUniqueId(uid)
|
169
|
+
end
|
170
|
+
def new_record?
|
171
|
+
!exists?
|
172
|
+
end
|
173
|
+
alias :new? :new_record?
|
174
|
+
|
175
|
+
def delete!
|
176
|
+
if exists?
|
177
|
+
address_book.removeRecord(ab_person)
|
178
|
+
address_book.save
|
179
|
+
@ab_person = nil
|
180
|
+
self
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# not supported on OSX
|
185
|
+
# def composite_name
|
186
|
+
# ABRecordCopyCompositeName(ab_person)
|
187
|
+
# end
|
188
|
+
|
189
|
+
def person?
|
190
|
+
(get_field(KABPersonFlags) & KABShowAsCompany == 0)
|
191
|
+
end
|
192
|
+
def organization?
|
193
|
+
(get_field(KABPersonFlags) & KABShowAsCompany == 1)
|
194
|
+
end
|
195
|
+
|
196
|
+
def modification_date
|
197
|
+
get_field(KABModificationDateProperty)
|
198
|
+
end
|
199
|
+
|
200
|
+
def creation_date
|
201
|
+
get_field(KABCreationDateProperty)
|
202
|
+
end
|
203
|
+
|
204
|
+
# replace *all* properties of an existing Person with new values
|
205
|
+
def replace(new_attributes)
|
206
|
+
@attributes = new_attributes
|
207
|
+
load_ab_person
|
208
|
+
end
|
209
|
+
|
210
|
+
def linked_people
|
211
|
+
recs = ab_person.linkedPeople
|
212
|
+
recs.delete(ab_person) # LinkedPeople always includes self
|
213
|
+
recs.map do |linked_rec|
|
214
|
+
Person.new(nil, linked_rec, :address_book => address_book)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def to_vcard
|
219
|
+
ab_person.vCardRepresentation
|
220
|
+
end
|
221
|
+
|
222
|
+
def groups
|
223
|
+
ab_person.parentGroups.map do |ab_group|
|
224
|
+
AddressBook::Group.new(:ab_group => ab_group, :address_book => address_book)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def local?
|
229
|
+
!ab_person.isReadOnly
|
230
|
+
end
|
231
|
+
|
232
|
+
def to_s
|
233
|
+
"#<#{self.class}:#{uid}: #{attributes}>"
|
234
|
+
end
|
235
|
+
alias :inspect :to_s
|
236
|
+
|
237
|
+
# private
|
238
|
+
|
239
|
+
SingleValuePropertyMap = {
|
240
|
+
KABFirstNameProperty => :first_name,
|
241
|
+
KABLastNameProperty => :last_name,
|
242
|
+
KABMiddleNameProperty => :middle_name,
|
243
|
+
KABPrefixProperty => :prefix,
|
244
|
+
KABSuffixProperty => :suffix,
|
245
|
+
KABNicknameProperty => :nickname,
|
246
|
+
KABJobTitleProperty => :job_title,
|
247
|
+
KABDepartmentProperty => :department,
|
248
|
+
KABOrganizationProperty => :organization,
|
249
|
+
KABBirthdayProperty => :birthday,
|
250
|
+
KABNoteProperty => :note
|
251
|
+
}
|
252
|
+
ReverseSingleValuePropertyMap = SingleValuePropertyMap.invert
|
253
|
+
|
254
|
+
MultiValuePropertyMap = {
|
255
|
+
KABPhoneProperty => :phones,
|
256
|
+
KABEmailProperty => :emails,
|
257
|
+
KABAddressProperty => :addresses,
|
258
|
+
KABURLsProperty => :urls,
|
259
|
+
KABSocialProfileProperty => :social_profiles,
|
260
|
+
KABInstantMessageProperty => :im_profiles,
|
261
|
+
KABRelatedNamesProperty => :related_names,
|
262
|
+
KABOtherDatesProperty => :dates
|
263
|
+
}
|
264
|
+
|
265
|
+
# instantiates ABPerson record from attributes
|
266
|
+
def initialize_ab_person
|
267
|
+
@ab_person = ABPerson.alloc.initWithAddressBook(address_book)
|
268
|
+
load_ab_person
|
269
|
+
@ab_person
|
270
|
+
end
|
271
|
+
|
272
|
+
def load_ab_person
|
273
|
+
@attributes ||= {}
|
274
|
+
|
275
|
+
SingleValuePropertyMap.each do |ab_property, attr_key|
|
276
|
+
if attributes[attr_key]
|
277
|
+
set_field(ab_property, attributes[attr_key])
|
278
|
+
else
|
279
|
+
remove_field(ab_property)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
if attributes[:is_org]
|
284
|
+
set_field(KABPersonFlags, KABShowAsCompany)
|
285
|
+
else
|
286
|
+
set_field(KABPersonFlags, KABShowAsPerson)
|
287
|
+
end
|
288
|
+
|
289
|
+
MultiValuePropertyMap.each do |ab_property, attr_key|
|
290
|
+
if attributes[attr_key]
|
291
|
+
set_multi_valued(ab_property, attributes[attr_key])
|
292
|
+
else
|
293
|
+
remove_field(ab_property)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
ab_person
|
298
|
+
end
|
299
|
+
|
300
|
+
# populate attributes from existing ABPerson
|
301
|
+
def import_ab_person
|
302
|
+
@attributes = {}
|
303
|
+
@modification_date = nil
|
304
|
+
|
305
|
+
SingleValuePropertyMap.each do |ab_property, attr_key|
|
306
|
+
if value = get_field(ab_property)
|
307
|
+
@attributes[attr_key] = value
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
if organization?
|
312
|
+
@attributes[:is_org] = true
|
313
|
+
end
|
314
|
+
|
315
|
+
MultiValuePropertyMap.each do |ab_property, attr_key|
|
316
|
+
if value = get_multi_valued(ab_property)
|
317
|
+
if value.attributes.any?
|
318
|
+
@attributes[attr_key] = value.attributes
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
@attributes
|
324
|
+
end
|
325
|
+
|
326
|
+
def set_field(field, value)
|
327
|
+
if value
|
328
|
+
ab_person.setValue(value, forProperty:field)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
def get_field(field)
|
332
|
+
ab_person.valueForProperty(field)
|
333
|
+
end
|
334
|
+
def remove_field(field)
|
335
|
+
ab_person.removeValueForProperty(field)
|
336
|
+
end
|
337
|
+
|
338
|
+
def set_multi_valued(field, values)
|
339
|
+
values = values.map { |value| ( (value.kind_of?String) ? {:value => value} : value)}
|
340
|
+
if values && values.any?
|
341
|
+
multi_field = MultiValued.new(:attributes => values)
|
342
|
+
set_field(field, multi_field.ab_multi_value)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def address_book
|
347
|
+
@address_book ||= AddressBook.address_book
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
data/motion/address_book.rb
CHANGED
@@ -2,15 +2,23 @@ module AddressBook
|
|
2
2
|
module_function
|
3
3
|
|
4
4
|
def address_book
|
5
|
-
if
|
6
|
-
|
7
|
-
else
|
8
|
-
|
5
|
+
if App.osx?
|
6
|
+
ABAddressBook.addressBook
|
7
|
+
else # iOS
|
8
|
+
if Device.ios_version == '6.0'
|
9
|
+
ios6_create
|
10
|
+
else
|
11
|
+
ios5_create
|
12
|
+
end
|
9
13
|
end
|
10
14
|
end
|
11
15
|
|
12
16
|
def count
|
13
|
-
|
17
|
+
if App.ios?
|
18
|
+
ABAddressBookGetPersonCount(address_book)
|
19
|
+
else
|
20
|
+
address_book.count
|
21
|
+
end
|
14
22
|
end
|
15
23
|
|
16
24
|
def ios6_create
|
data/motion-addressbook.gemspec
CHANGED
@@ -7,8 +7,8 @@ Gem::Specification.new do |gem|
|
|
7
7
|
|
8
8
|
gem.authors = ["Alex Rothenberg", "Jason May"]
|
9
9
|
gem.email = ["alex@alexrothenberg.com", "jmay@pobox.com"]
|
10
|
-
gem.description = %q{A RubyMotion wrapper around the iOS Address Book
|
11
|
-
gem.summary = %q{A RubyMotion wrapper around the iOS Address Book
|
10
|
+
gem.description = %q{A RubyMotion wrapper around the iOS & OSX Address Book frameworks}
|
11
|
+
gem.summary = %q{A RubyMotion wrapper around the iOS & OSX Address Book frameworks}
|
12
12
|
gem.homepage = ""
|
13
13
|
|
14
14
|
gem.files = `git ls-files`.split($\)
|
File without changes
|
File without changes
|