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.
Files changed (31) hide show
  1. data/.travis.yml +0 -1
  2. data/README.md +2 -3
  3. data/Rakefile +16 -4
  4. data/lib/motion-addressbook/version.rb +1 -1
  5. data/lib/motion-addressbook.rb +22 -10
  6. data/motion/address_book/{addr_book.rb → ios/addr_book.rb} +0 -0
  7. data/motion/address_book/{group.rb → ios/group.rb} +0 -0
  8. data/motion/address_book/{multi_value.rb → ios/multi_value.rb} +0 -0
  9. data/motion/address_book/{multi_valued.rb → ios/multi_valued.rb} +0 -0
  10. data/motion/address_book/{person.rb → ios/person.rb} +8 -0
  11. data/motion/address_book/{picker.rb → ios/picker.rb} +0 -0
  12. data/motion/address_book/{source.rb → ios/source.rb} +0 -0
  13. data/motion/address_book/osx/addr_book.rb +67 -0
  14. data/motion/address_book/osx/group.rb +122 -0
  15. data/motion/address_book/osx/multi_valued.rb +166 -0
  16. data/motion/address_book/osx/person.rb +350 -0
  17. data/motion/address_book.rb +13 -5
  18. data/motion-addressbook.gemspec +2 -2
  19. data/spec/{address_book → ios/address_book}/group_spec.rb +0 -0
  20. data/spec/{address_book → ios/address_book}/multi_valued_spec.rb +0 -0
  21. data/spec/{address_book → ios/address_book}/person_spec.rb +33 -154
  22. data/spec/{address_book → ios/address_book}/picker_spec.rb +0 -0
  23. data/spec/{helpers → ios/helpers}/bacon_matchers.rb +0 -0
  24. data/spec/{helpers → ios/helpers}/hacks.rb +0 -0
  25. data/spec/{helpers → ios/helpers}/person_helpers.rb +0 -0
  26. data/spec/osx/address_book/person_spec.rb +174 -0
  27. data/spec/osx/helpers/bacon_matchers.rb +11 -0
  28. data/spec/osx/helpers/hacks.rb +6 -0
  29. data/spec/osx/helpers/person_helpers.rb +12 -0
  30. metadata +39 -29
  31. 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
@@ -2,15 +2,23 @@ module AddressBook
2
2
  module_function
3
3
 
4
4
  def address_book
5
- if UIDevice.currentDevice.systemVersion >= '6'
6
- ios6_create
7
- else
8
- ios5_create
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
- ABAddressBookGetPersonCount(address_book)
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
@@ -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 framework}
11
- gem.summary = %q{A RubyMotion wrapper around the iOS Address Book framework}
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($\)