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.
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