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
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
|
-
[](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
|