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
data/.travis.yml CHANGED
@@ -1,5 +1,4 @@
1
1
  language: objective-c
2
- before_install: rvm use 1.9.3
3
2
  notifications:
4
3
  email:
5
4
  recipients:
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 [Address Book Programming Guide for iOS](http://developer.apple.com/library/ios/#DOCUMENTATION/ContactData/Conceptual/AddressBookProgrammingGuideforiPhone/Introduction.html)
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
- require 'bubble-wrap/test'
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
@@ -1,5 +1,5 @@
1
1
  module Motion
2
2
  module Addressbook
3
- VERSION = "1.4.0"
3
+ VERSION = "1.5.0"
4
4
  end
5
5
  end
@@ -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
- BW.require 'motion/address_book/picker.rb' do
14
- file('motion/address_book/picker.rb').uses_framework('AddressBookUI')
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
- Motion::Project::App.setup do |app|
18
- app.vendor_project(File.expand_path(File.join(File.dirname(__FILE__), '../abhack')), :static)
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
@@ -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