bubble-wrap 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +6 -14
- data/.travis.yml +2 -1
- data/CHANGELOG.md +26 -2
- data/GETTING_STARTED.md +1 -1
- data/Gemfile.lock +1 -1
- data/HACKING.md +6 -4
- data/README.md +83 -3
- data/Rakefile +9 -7
- data/lib/bubble-wrap/all.rb +2 -2
- data/lib/bubble-wrap/mail.rb +9 -0
- data/lib/bubble-wrap/sms.rb +9 -0
- data/lib/bubble-wrap/ui.rb +6 -2
- data/lib/bubble-wrap/version.rb +1 -1
- data/motion/core.rb +1 -1
- data/motion/core/app.rb +7 -2
- data/motion/core/device/ios/camera.rb +3 -2
- data/motion/core/device/ios/screen.rb +19 -0
- data/motion/core/ios/app.rb +13 -1
- data/motion/core/ios/device.rb +5 -0
- data/motion/core/json.rb +1 -3
- data/motion/core/kvo.rb +11 -13
- data/motion/core/persistence.rb +8 -1
- data/motion/core/string.rb +3 -3
- data/motion/core/time.rb +5 -0
- data/motion/http.rb +1 -1
- data/motion/http/query.rb +39 -19
- data/motion/http/response.rb +4 -4
- data/motion/mail/mail.rb +59 -0
- data/motion/mail/result.rb +29 -0
- data/motion/reactor/eventable.rb +3 -2
- data/motion/reactor/periodic_timer.rb +12 -8
- data/motion/reactor/timer.rb +11 -7
- data/motion/sms/result.rb +24 -0
- data/motion/sms/sms.rb +47 -0
- data/motion/ui/pollute.rb +4 -1
- data/motion/ui/ui_alert_view.rb +15 -8
- data/motion/ui/ui_control_wrapper.rb +16 -0
- data/motion/ui/ui_view_controller_wrapper.rb +13 -0
- data/motion/ui/ui_view_wrapper.rb +55 -0
- data/resources/Localizable.strings +1 -0
- data/samples/gesture/Gemfile +2 -2
- data/samples/location/Gemfile +1 -1
- data/samples/osx/Gemfile +2 -2
- data/spec/motion/core/app_spec.rb +6 -0
- data/spec/motion/core/device/ios/camera_spec.rb +3 -3
- data/spec/motion/core/device/ios/screen_spec.rb +45 -0
- data/spec/motion/core/ios/app_spec.rb +29 -0
- data/spec/motion/core/persistence_spec.rb +25 -0
- data/spec/motion/core/string_spec.rb +2 -2
- data/spec/motion/core/time_spec.rb +14 -4
- data/spec/motion/core_spec.rb +9 -8
- data/spec/motion/http/query_spec.rb +92 -15
- data/spec/motion/http/response_spec.rb +4 -3
- data/spec/motion/location/location_spec.rb +1 -1
- data/spec/motion/mail/mail_spec.rb +125 -0
- data/spec/motion/mail/result_spec.rb +53 -0
- data/spec/motion/reactor/eventable_spec.rb +85 -0
- data/spec/motion/reactor_spec.rb +0 -20
- data/spec/motion/sms/result_spec.rb +38 -0
- data/spec/motion/sms/sms_spec.rb +71 -0
- data/spec/motion/ui/pollute_spec.rb +13 -0
- data/spec/motion/ui/ui_bar_button_item_spec.rb +8 -8
- data/spec/motion/ui/ui_control_wrapper_spec.rb +35 -0
- data/spec/motion/ui/ui_view_controller_wrapper_spec.rb +34 -0
- data/spec/motion/ui/ui_view_wrapper_spec.rb +62 -0
- data/travis.sh +7 -0
- metadata +52 -22
- data/motion/ui/gestures.rb +0 -57
- data/motion/ui/ui_control.rb +0 -14
- data/motion/ui/ui_view_controller.rb +0 -11
- data/spec/motion/ui/gestures_spec.rb +0 -62
- data/spec/motion/ui/ui_control_spec.rb +0 -42
data/motion/core/ios/app.rb
CHANGED
@@ -63,9 +63,21 @@ module BubbleWrap
|
|
63
63
|
UIApplication.sharedApplication
|
64
64
|
end
|
65
65
|
|
66
|
+
def windows
|
67
|
+
UIApplication.sharedApplication.windows
|
68
|
+
end
|
69
|
+
|
66
70
|
# the Application Window
|
67
71
|
def window
|
68
|
-
|
72
|
+
normal_windows = App.windows.select { |w|
|
73
|
+
w.windowLevel == UIWindowLevelNormal
|
74
|
+
}
|
75
|
+
|
76
|
+
key_window = normal_windows.select {|w|
|
77
|
+
w == UIApplication.sharedApplication.keyWindow
|
78
|
+
}.first
|
79
|
+
|
80
|
+
key_window || normal_windows.first
|
69
81
|
end
|
70
82
|
end
|
71
83
|
end
|
data/motion/core/ios/device.rb
CHANGED
data/motion/core/json.rb
CHANGED
@@ -28,9 +28,7 @@ module BubbleWrap
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.generate(obj)
|
31
|
-
|
32
|
-
data = NSJSONSerialization.dataWithJSONObject(obj, options:0, error:nil)
|
33
|
-
data.to_str
|
31
|
+
NSJSONSerialization.dataWithJSONObject(obj, options:0, error:nil).to_str
|
34
32
|
end
|
35
33
|
|
36
34
|
end
|
data/motion/core/kvo.rb
CHANGED
@@ -64,9 +64,7 @@ module BubbleWrap
|
|
64
64
|
return if @targets.nil? || target.nil? || key_path.nil?
|
65
65
|
|
66
66
|
key_paths = @targets[target]
|
67
|
-
if !key_paths.nil?
|
68
|
-
key_paths.delete(key_path.to_s)
|
69
|
-
end
|
67
|
+
key_paths.delete(key_path.to_s) if !key_paths.nil?
|
70
68
|
end
|
71
69
|
|
72
70
|
def remove_all_observer_blocks
|
@@ -75,19 +73,19 @@ module BubbleWrap
|
|
75
73
|
|
76
74
|
# NSKeyValueObserving Protocol
|
77
75
|
|
78
|
-
def observeValueForKeyPath(key_path, ofObject:target, change:change, context:context)
|
76
|
+
def observeValueForKeyPath(key_path, ofObject: target, change: change, context: context)
|
79
77
|
key_paths = @targets[target] || {}
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
78
|
+
blocks = key_paths[key_path] || []
|
79
|
+
blocks.each do |block|
|
80
|
+
args = [change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey]]
|
81
|
+
args << change[NSKeyValueChangeIndexesKey] if collection?(change)
|
82
|
+
block.call(*args)
|
86
83
|
end
|
84
|
+
end
|
87
85
|
|
88
|
-
|
89
|
-
|
90
|
-
|
86
|
+
def collection?(change)
|
87
|
+
COLLECTION_OPERATIONS.include?(change[NSKeyValueChangeKindKey])
|
88
|
+
end
|
91
89
|
|
92
90
|
end
|
93
91
|
end
|
data/motion/core/persistence.rb
CHANGED
@@ -29,12 +29,19 @@ module BubbleWrap
|
|
29
29
|
storage.synchronize
|
30
30
|
end
|
31
31
|
|
32
|
+
def delete(key)
|
33
|
+
value = storage.objectForKey storage_key(key)
|
34
|
+
storage.removeObjectForKey(storage_key(key))
|
35
|
+
storage.synchronize
|
36
|
+
value
|
37
|
+
end
|
38
|
+
|
32
39
|
def storage
|
33
40
|
NSUserDefaults.standardUserDefaults
|
34
41
|
end
|
35
42
|
|
36
43
|
def storage_key(key)
|
37
|
-
app_key
|
44
|
+
"#{app_key}_#{key}"
|
38
45
|
end
|
39
46
|
end
|
40
47
|
|
data/motion/core/string.rb
CHANGED
@@ -55,9 +55,9 @@ module BubbleWrap
|
|
55
55
|
hex_color = self.gsub("#", "")
|
56
56
|
case hex_color.size
|
57
57
|
when 3
|
58
|
-
colors = hex_color.scan(%r{[0-9A-Fa-f]}).map{ |el| (el * 2).to_i(16) }
|
58
|
+
colors = hex_color.scan(%r{[0-9A-Fa-f]}).map!{ |el| (el * 2).to_i(16) }
|
59
59
|
when 6
|
60
|
-
colors = hex_color.scan(%r<[0-9A-Fa-f]{2}>).map{ |el| el.to_i(16) }
|
60
|
+
colors = hex_color.scan(%r<[0-9A-Fa-f]{2}>).map!{ |el| el.to_i(16) }
|
61
61
|
else
|
62
62
|
raise ArgumentError
|
63
63
|
end
|
@@ -71,4 +71,4 @@ module BubbleWrap
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
|
74
|
+
NSString.send(:include, BubbleWrap::String)
|
data/motion/core/time.rb
CHANGED
data/motion/http.rb
CHANGED
@@ -19,7 +19,7 @@ module BubbleWrap
|
|
19
19
|
# end
|
20
20
|
#
|
21
21
|
|
22
|
-
[:get, :post, :put, :delete, :head, :patch].each do |http_verb|
|
22
|
+
[:get, :post, :put, :delete, :head, :options, :patch].each do |http_verb|
|
23
23
|
|
24
24
|
define_singleton_method(http_verb) do |url, options = {}, &block|
|
25
25
|
options[:action] = block if block
|
data/motion/http/query.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Class wrapping NSConnection and often used indirectly by the BubbleWrap::HTTP module methods.
|
2
|
-
|
2
|
+
module BubbleWrap; module HTTP; class Query
|
3
3
|
attr_accessor :request
|
4
4
|
attr_accessor :connection
|
5
5
|
attr_accessor :credentials # username & password has a hash
|
@@ -20,7 +20,7 @@ class BubbleWrap::HTTP::Query
|
|
20
20
|
#
|
21
21
|
# ==== Options
|
22
22
|
# :payload<String> - data to pass to a POST, PUT, DELETE query.
|
23
|
-
# :
|
23
|
+
# :action - Proc, class or object to call when the file is downloaded.
|
24
24
|
# a proc will receive a Response object while the passed object
|
25
25
|
# will receive the handle_query_response method
|
26
26
|
# :headers<Hash> - headers send with the request
|
@@ -31,6 +31,7 @@ class BubbleWrap::HTTP::Query
|
|
31
31
|
@method = http_method.upcase.to_s
|
32
32
|
@delegator = options.delete(:action) || self
|
33
33
|
@payload = options.delete(:payload)
|
34
|
+
@encoding = options.delete(:encoding) || NSUTF8StringEncoding
|
34
35
|
@files = options.delete(:files)
|
35
36
|
@boundary = options.delete(:boundary) || BW.create_uuid
|
36
37
|
@credentials = options.delete(:credentials) || {}
|
@@ -70,9 +71,7 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
70
71
|
if App.osx? && !response.is_a?(NSHTTPURLResponse)
|
71
72
|
return
|
72
73
|
end
|
73
|
-
|
74
|
-
@response_headers = response.allHeaderFields
|
75
|
-
@response_size = response.expectedContentLength.to_f
|
74
|
+
did_receive_response(response)
|
76
75
|
end
|
77
76
|
|
78
77
|
# This delegate method get called every time a chunk of data is being received
|
@@ -95,7 +94,11 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
95
94
|
log "##{@redirect_count} HTTP redirect_count: #{request.inspect} - #{self.description}"
|
96
95
|
|
97
96
|
if @redirect_count >= 30
|
98
|
-
@response.
|
97
|
+
@response.error = NSError.errorWithDomain('BubbleWrap::HTTP', code:NSURLErrorHTTPTooManyRedirects,
|
98
|
+
userInfo:NSDictionary.dictionaryWithObject("Too many redirections",
|
99
|
+
forKey: NSLocalizedDescriptionKey))
|
100
|
+
@response.error_message = @response.error.localizedDescription
|
101
|
+
show_status_indicator false
|
99
102
|
@request.done_loading!
|
100
103
|
call_delegator_with_response
|
101
104
|
nil
|
@@ -109,6 +112,7 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
109
112
|
log "HTTP Connection to #{@url.absoluteString} failed #{error.localizedDescription}"
|
110
113
|
show_status_indicator false
|
111
114
|
@request.done_loading!
|
115
|
+
@response.error = error
|
112
116
|
@response.error_message = error.localizedDescription
|
113
117
|
call_delegator_with_response
|
114
118
|
end
|
@@ -139,15 +143,27 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
139
143
|
log "auth challenged, answered with credentials: #{credentials.inspect}"
|
140
144
|
end
|
141
145
|
else
|
146
|
+
did_receive_response(challenge.failureResponse)
|
147
|
+
@response.update(status_code: status_code, headers: response_headers, url: @url, original_url: @original_url)
|
142
148
|
challenge.sender.cancelAuthenticationChallenge(challenge)
|
143
149
|
log 'Auth Failed :('
|
144
150
|
end
|
145
151
|
end
|
146
152
|
|
153
|
+
def cancel
|
154
|
+
@connection.cancel
|
155
|
+
show_status_indicator false
|
156
|
+
@request.done_loading!
|
157
|
+
end
|
147
158
|
|
148
159
|
private
|
149
160
|
|
150
|
-
|
161
|
+
def did_receive_response(response)
|
162
|
+
@status_code = response.statusCode
|
163
|
+
@response_headers = response.allHeaderFields
|
164
|
+
@response_size = response.expectedContentLength.to_f
|
165
|
+
end
|
166
|
+
|
151
167
|
def show_status_indicator(show)
|
152
168
|
if App.ios?
|
153
169
|
UIApplication.sharedApplication.networkActivityIndicatorVisible = show
|
@@ -173,7 +189,7 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
173
189
|
|
174
190
|
def set_content_type
|
175
191
|
return if headers_provided?
|
176
|
-
return if (@method == "GET" || @method == "HEAD")
|
192
|
+
return if (@method == "GET" || @method == "HEAD" || @method == "OPTIONS")
|
177
193
|
@headers ||= {}
|
178
194
|
@headers["Content-Type"] = case @format
|
179
195
|
when :json
|
@@ -200,7 +216,7 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
200
216
|
end
|
201
217
|
|
202
218
|
def create_request_body
|
203
|
-
return nil if (@method == "GET" || @method == "HEAD")
|
219
|
+
return nil if (@method == "GET" || @method == "HEAD" || @method == "OPTIONS")
|
204
220
|
return nil unless (@payload || @files)
|
205
221
|
|
206
222
|
body = NSMutableData.data
|
@@ -217,7 +233,10 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
217
233
|
if @payload.is_a?(NSData)
|
218
234
|
body.appendData(@payload)
|
219
235
|
elsif @payload.is_a?(String)
|
220
|
-
body.appendData(@payload.
|
236
|
+
body.appendData(@payload.to_encoded_data @encoding)
|
237
|
+
elsif @format == :json
|
238
|
+
json_string = BW::JSON.generate(@payload)
|
239
|
+
body.appendData(json_string.to_encoded_data @encoding)
|
221
240
|
else
|
222
241
|
append_form_params(body)
|
223
242
|
end
|
@@ -231,7 +250,7 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
231
250
|
s += "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
|
232
251
|
s += value.to_s
|
233
252
|
s += "\r\n"
|
234
|
-
body.appendData(s.
|
253
|
+
body.appendData(s.to_encoded_data @encoding)
|
235
254
|
end
|
236
255
|
@payload_or_files_were_appended = true
|
237
256
|
body
|
@@ -251,7 +270,7 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
251
270
|
|
252
271
|
def parse_file(key, value)
|
253
272
|
if value.is_a?(Hash)
|
254
|
-
raise
|
273
|
+
raise(InvalidFileError, "You need to supply a `:data` entry in #{value} for file '#{key}' in your HTTP `:files`") if value[:data].nil?
|
255
274
|
{data: value[:data], filename: value[:filename] || key}
|
256
275
|
else
|
257
276
|
{data: value, filename: key}
|
@@ -265,9 +284,9 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
265
284
|
s += "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{file[:filename]}\"\r\n"
|
266
285
|
s += "Content-Type: application/octet-stream\r\n\r\n"
|
267
286
|
file_data = NSMutableData.new
|
268
|
-
file_data.appendData(s.
|
287
|
+
file_data.appendData(s.to_encoded_data @encoding)
|
269
288
|
file_data.appendData(file[:data])
|
270
|
-
file_data.appendData("\r\n".
|
289
|
+
file_data.appendData("\r\n".to_encoded_data @encoding)
|
271
290
|
body.appendData(file_data)
|
272
291
|
end
|
273
292
|
@payload_or_files_were_appended = true
|
@@ -275,12 +294,12 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
275
294
|
end
|
276
295
|
|
277
296
|
def append_body_boundary(body)
|
278
|
-
body.appendData("--#{@boundary}--\r\n".
|
297
|
+
body.appendData("--#{@boundary}--\r\n".to_encoded_data @encoding)
|
279
298
|
end
|
280
299
|
|
281
300
|
def create_url(url_string)
|
282
301
|
url_string = url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding
|
283
|
-
if (@method == "GET" || @method == "HEAD") && @payload
|
302
|
+
if (@method == "GET" || @method == "HEAD" || @method == "OPTIONS") && @payload
|
284
303
|
unless @payload.empty?
|
285
304
|
convert_payload_to_url if @payload.is_a?(Hash)
|
286
305
|
url_string += "?#{@payload}"
|
@@ -299,8 +318,9 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
299
318
|
end
|
300
319
|
|
301
320
|
def escape(string)
|
302
|
-
|
303
|
-
|
321
|
+
string_to_escape = string.to_s
|
322
|
+
if string_to_escape
|
323
|
+
CFURLCreateStringByAddingPercentEscapes nil, string_to_escape, nil, "!*'();:@&=+$,/?%#[]", KCFStringEncodingUTF8
|
304
324
|
end
|
305
325
|
end
|
306
326
|
|
@@ -364,4 +384,4 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
364
384
|
NSURLConnection.connectionWithRequest(request, delegate:delegate)
|
365
385
|
end
|
366
386
|
|
367
|
-
end
|
387
|
+
end; end; end
|
data/motion/http/response.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Response class wrapping the results of a Query's response
|
2
|
-
|
2
|
+
module BubbleWrap; module HTTP; class Response
|
3
3
|
attr_reader :body
|
4
4
|
attr_reader :headers
|
5
|
-
attr_accessor :status_code, :status_description, :error_message
|
5
|
+
attr_accessor :status_code, :status_description, :error_message, :error
|
6
6
|
attr_reader :url
|
7
7
|
attr_reader :original_url
|
8
8
|
|
@@ -18,7 +18,7 @@ class BubbleWrap::HTTP::Response
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def ok?
|
21
|
-
status_code.to_s =~ /
|
21
|
+
status_code.to_s =~ /2\d\d/ ? true : false
|
22
22
|
end
|
23
23
|
|
24
24
|
def to_s
|
@@ -29,4 +29,4 @@ class BubbleWrap::HTTP::Response
|
|
29
29
|
def update_status_description
|
30
30
|
@status_description = status_code.nil? ? nil : NSHTTPURLResponse.localizedStringForStatusCode(status_code)
|
31
31
|
end
|
32
|
-
end
|
32
|
+
end; end; end
|
data/motion/mail/mail.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module BubbleWrap
|
2
|
+
module Mail
|
3
|
+
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# Base method to create your in-app mail
|
7
|
+
# ---------------------------------------
|
8
|
+
# EX
|
9
|
+
# BW::Mail.compose {
|
10
|
+
# delegate: self, # optional, will use root view controller by default
|
11
|
+
# to: [ "tom@example.com" ],
|
12
|
+
# cc: [ "itchy@example.com", "scratchy@example.com" ],
|
13
|
+
# bcc: [ "jerry@example.com" ],
|
14
|
+
# html: false,
|
15
|
+
# subject: "My Subject",
|
16
|
+
# message: "This is my message. It isn't very long.",
|
17
|
+
# animated: false
|
18
|
+
# } do |result, error|
|
19
|
+
# result.sent? # => boolean
|
20
|
+
# result.canceled? # => boolean
|
21
|
+
# result.saved? # => boolean
|
22
|
+
# result.failed? # => boolean
|
23
|
+
# error # => NSError
|
24
|
+
# end
|
25
|
+
def compose(options={}, &callback)
|
26
|
+
@delegate = options[:delegate] || App.window.rootViewController
|
27
|
+
|
28
|
+
@callback = callback
|
29
|
+
|
30
|
+
@mail_controller = create_mail_controller(options)
|
31
|
+
|
32
|
+
@mailer_is_animated = options[:animated] == false ? false : true
|
33
|
+
@delegate.presentModalViewController(@mail_controller, animated: @mailer_is_animated)
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_mail_controller(options={})
|
37
|
+
mail_controller = MFMailComposeViewController.alloc.init
|
38
|
+
|
39
|
+
mail_controller.mailComposeDelegate = self
|
40
|
+
mail_controller.setToRecipients(Array(options[:to]))
|
41
|
+
mail_controller.setCcRecipients(Array(options[:cc]))
|
42
|
+
mail_controller.setBccRecipients(Array(options[:bcc]))
|
43
|
+
mail_controller.setSubject(options[:subject] || "Contact")
|
44
|
+
is_html = !!options[:html]
|
45
|
+
mail_controller.setMessageBody(options[:message], isHTML: is_html)
|
46
|
+
|
47
|
+
mail_controller
|
48
|
+
end
|
49
|
+
|
50
|
+
# Event when the MFMailComposeViewController is closed
|
51
|
+
# -------------------------------------------------------------
|
52
|
+
# the callback is fired if it was present in the constructor
|
53
|
+
|
54
|
+
def mailComposeController(controller, didFinishWithResult: result, error: error)
|
55
|
+
@delegate.dismissModalViewControllerAnimated(@mailer_is_animated)
|
56
|
+
@callback.call Result.new(result, error) if @callback
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module BubbleWrap
|
2
|
+
module Mail
|
3
|
+
class Result
|
4
|
+
attr_accessor :result, :error
|
5
|
+
|
6
|
+
def initialize(result, error)
|
7
|
+
self.result = result
|
8
|
+
self.error = error
|
9
|
+
end
|
10
|
+
|
11
|
+
def sent?
|
12
|
+
self.result == MFMailComposeResultSent
|
13
|
+
end
|
14
|
+
|
15
|
+
def canceled?
|
16
|
+
self.result == MFMailComposeResultCancelled
|
17
|
+
end
|
18
|
+
|
19
|
+
def saved?
|
20
|
+
self.result == MFMailComposeResultSaved
|
21
|
+
end
|
22
|
+
|
23
|
+
def failed?
|
24
|
+
self.result == MFMailComposeResultFailed || self.error
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/motion/reactor/eventable.rb
CHANGED