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.
Files changed (72) hide show
  1. checksums.yaml +6 -14
  2. data/.travis.yml +2 -1
  3. data/CHANGELOG.md +26 -2
  4. data/GETTING_STARTED.md +1 -1
  5. data/Gemfile.lock +1 -1
  6. data/HACKING.md +6 -4
  7. data/README.md +83 -3
  8. data/Rakefile +9 -7
  9. data/lib/bubble-wrap/all.rb +2 -2
  10. data/lib/bubble-wrap/mail.rb +9 -0
  11. data/lib/bubble-wrap/sms.rb +9 -0
  12. data/lib/bubble-wrap/ui.rb +6 -2
  13. data/lib/bubble-wrap/version.rb +1 -1
  14. data/motion/core.rb +1 -1
  15. data/motion/core/app.rb +7 -2
  16. data/motion/core/device/ios/camera.rb +3 -2
  17. data/motion/core/device/ios/screen.rb +19 -0
  18. data/motion/core/ios/app.rb +13 -1
  19. data/motion/core/ios/device.rb +5 -0
  20. data/motion/core/json.rb +1 -3
  21. data/motion/core/kvo.rb +11 -13
  22. data/motion/core/persistence.rb +8 -1
  23. data/motion/core/string.rb +3 -3
  24. data/motion/core/time.rb +5 -0
  25. data/motion/http.rb +1 -1
  26. data/motion/http/query.rb +39 -19
  27. data/motion/http/response.rb +4 -4
  28. data/motion/mail/mail.rb +59 -0
  29. data/motion/mail/result.rb +29 -0
  30. data/motion/reactor/eventable.rb +3 -2
  31. data/motion/reactor/periodic_timer.rb +12 -8
  32. data/motion/reactor/timer.rb +11 -7
  33. data/motion/sms/result.rb +24 -0
  34. data/motion/sms/sms.rb +47 -0
  35. data/motion/ui/pollute.rb +4 -1
  36. data/motion/ui/ui_alert_view.rb +15 -8
  37. data/motion/ui/ui_control_wrapper.rb +16 -0
  38. data/motion/ui/ui_view_controller_wrapper.rb +13 -0
  39. data/motion/ui/ui_view_wrapper.rb +55 -0
  40. data/resources/Localizable.strings +1 -0
  41. data/samples/gesture/Gemfile +2 -2
  42. data/samples/location/Gemfile +1 -1
  43. data/samples/osx/Gemfile +2 -2
  44. data/spec/motion/core/app_spec.rb +6 -0
  45. data/spec/motion/core/device/ios/camera_spec.rb +3 -3
  46. data/spec/motion/core/device/ios/screen_spec.rb +45 -0
  47. data/spec/motion/core/ios/app_spec.rb +29 -0
  48. data/spec/motion/core/persistence_spec.rb +25 -0
  49. data/spec/motion/core/string_spec.rb +2 -2
  50. data/spec/motion/core/time_spec.rb +14 -4
  51. data/spec/motion/core_spec.rb +9 -8
  52. data/spec/motion/http/query_spec.rb +92 -15
  53. data/spec/motion/http/response_spec.rb +4 -3
  54. data/spec/motion/location/location_spec.rb +1 -1
  55. data/spec/motion/mail/mail_spec.rb +125 -0
  56. data/spec/motion/mail/result_spec.rb +53 -0
  57. data/spec/motion/reactor/eventable_spec.rb +85 -0
  58. data/spec/motion/reactor_spec.rb +0 -20
  59. data/spec/motion/sms/result_spec.rb +38 -0
  60. data/spec/motion/sms/sms_spec.rb +71 -0
  61. data/spec/motion/ui/pollute_spec.rb +13 -0
  62. data/spec/motion/ui/ui_bar_button_item_spec.rb +8 -8
  63. data/spec/motion/ui/ui_control_wrapper_spec.rb +35 -0
  64. data/spec/motion/ui/ui_view_controller_wrapper_spec.rb +34 -0
  65. data/spec/motion/ui/ui_view_wrapper_spec.rb +62 -0
  66. data/travis.sh +7 -0
  67. metadata +52 -22
  68. data/motion/ui/gestures.rb +0 -57
  69. data/motion/ui/ui_control.rb +0 -14
  70. data/motion/ui/ui_view_controller.rb +0 -11
  71. data/spec/motion/ui/gestures_spec.rb +0 -62
  72. data/spec/motion/ui/ui_control_spec.rb +0 -42
@@ -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
- UIApplication.sharedApplication.keyWindow || UIApplication.sharedApplication.windows[0]
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
@@ -55,5 +55,10 @@ module BubbleWrap
55
55
  def orientation
56
56
  screen.orientation
57
57
  end
58
+
59
+ # Delegates to BubbleWrap::Screen.interface_orientation
60
+ def interface_orientation
61
+ screen.interface_orientation
62
+ end
58
63
  end
59
64
  end
@@ -28,9 +28,7 @@ module BubbleWrap
28
28
  end
29
29
 
30
30
  def self.generate(obj)
31
- # opts = NSJSONWritingPrettyPrinted
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
@@ -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? && key_paths.has_key?(key_path.to_s)
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
- blocks = key_paths[key_path] || []
81
- blocks.each do |block|
82
- args = [ change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey] ]
83
- args << change[NSKeyValueChangeIndexesKey] if collection?(change)
84
- block.call(*args)
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
- def collection?(change)
89
- COLLECTION_OPERATIONS.include?(change[NSKeyValueChangeKindKey])
90
- end
86
+ def collection?(change)
87
+ COLLECTION_OPERATIONS.include?(change[NSKeyValueChangeKindKey])
88
+ end
91
89
 
92
90
  end
93
91
  end
@@ -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 + '_' + key.to_s
44
+ "#{app_key}_#{key}"
38
45
  end
39
46
  end
40
47
 
@@ -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
- String.send(:include, BubbleWrap::String)
74
+ NSString.send(:include, BubbleWrap::String)
@@ -4,6 +4,11 @@ class Time
4
4
  cached_date_formatter("yyyy-MM-dd'T'HH:mm:ss'Z'").
5
5
  dateFromString(time)
6
6
  end
7
+
8
+ def self.iso8601_with_timezone(time)
9
+ cached_date_formatter("yyyy-MM-dd'T'HH:mm:ssZZZZZ").
10
+ dateFromString(time)
11
+ end
7
12
 
8
13
  private
9
14
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # Class wrapping NSConnection and often used indirectly by the BubbleWrap::HTTP module methods.
2
- class BubbleWrap::HTTP::Query
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
- # :delegator - Proc, class or object to call when the file is downloaded.
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
- @status_code = response.statusCode
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.error_message = "Too many redirections"
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
- private
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.dataUsingEncoding NSUTF8StringEncoding)
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.dataUsingEncoding NSUTF8StringEncoding)
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 InvalidFileError if value[:data].nil?
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.dataUsingEncoding NSUTF8StringEncoding)
287
+ file_data.appendData(s.to_encoded_data @encoding)
269
288
  file_data.appendData(file[:data])
270
- file_data.appendData("\r\n".dataUsingEncoding NSUTF8StringEncoding)
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".dataUsingEncoding NSUTF8StringEncoding)
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
- if string
303
- CFURLCreateStringByAddingPercentEscapes nil, string.to_s, nil, "!*'();:@&=+$,/?%#[]", KCFStringEncodingUTF8
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
@@ -1,8 +1,8 @@
1
1
  # Response class wrapping the results of a Query's response
2
- class BubbleWrap::HTTP::Response
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 =~ /20\d/ ? true : false
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
@@ -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
@@ -19,8 +19,9 @@ module BubbleWrap
19
19
 
20
20
  # Trigger an event
21
21
  def trigger(event, *args)
22
- __events__[event].map do |event|
23
- event.call(*args)
22
+ blks = __events__[event].clone
23
+ blks.map do |blk|
24
+ blk.call(*args)
24
25
  end
25
26
  end
26
27