bubble-wrap 0.2.1 → 0.3.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/.gitignore CHANGED
@@ -3,3 +3,5 @@ pkg/*
3
3
  Gemfile.lock
4
4
  build/
5
5
  .DS_Store
6
+ .repl_history
7
+ 0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.3.0
2
+
3
+ * Major refactoring preparing for 1.0: https://github.com/mattetti/BubbleWrap/compare/v0.2.1…v0.3.0
4
+
1
5
  ## 0.2.1
2
6
 
3
7
  * Minor fix in the way the dependencies are set (had to monkey patch
data/README.md CHANGED
@@ -14,6 +14,8 @@ gem install bubble-wrap
14
14
  ```ruby
15
15
  require 'bubble-wrap'
16
16
  ```
17
+ Note: **DON'T** use `app.files =` in your Rakefile to set up your files once you've required BubbleWrap.
18
+ Make sure to append onto the array or use `+=`.
17
19
 
18
20
  2. Now, you can use BubbleWrap extension in your app:
19
21
 
@@ -65,28 +67,36 @@ end
65
67
 
66
68
  `BubbleWrap::JSON` wraps `NSJSONSerialization` available in iOS5 and offers the same API as Ruby's JSON std lib.
67
69
 
68
- ## Kernel
70
+ ## Device
69
71
 
70
- A collection of useful methods used often in my RubyMotion apps.
72
+ A collection of useful methods about the current device:
71
73
 
72
74
  Examples:
73
75
  ```ruby
74
- > iphone?
76
+ > Device.iphone?
75
77
  # true
76
- > ipad?
78
+ > Device.ipad?
77
79
  # false
78
- > orientation
80
+ > Device.orientation
79
81
  # :portrait
80
- > simulator?
82
+ > Device.simulator?
81
83
  # true
82
- > documents_path
83
- # "/Users/mattetti/Library/Application Support/iPhone Simulator/5.0/Applications/EEC6454E-1816-451E-BB9A-EE18222E1A8F/Documents"
84
84
  ```
85
85
 
86
86
  ## App
87
87
 
88
- A module allowing developers to store global states and also provides a
89
- persistence layer.
88
+ A module with useful methods related to the running application
89
+
90
+ ```ruby
91
+ > App.documents_path
92
+ # "/Users/mattetti/Library/Application Support/iPhone Simulator/5.0/Applications/EEC6454E-1816-451E-BB9A-EE18222E1A8F/Documents"
93
+ > App.resources_path
94
+ # "/Users/mattetti/Library/Application Support/iPhone Simulator/5.0/Applications/EEC6454E-1816-451E-BB9A-EE18222E1A8F/testSuite_spec.app"
95
+ > App.name
96
+ # "testSuite"
97
+ > App.identifier
98
+ # "io.bubblewrap.testSuite"
99
+ ```
90
100
 
91
101
  ## NSUserDefaults
92
102
 
@@ -94,7 +104,7 @@ Helper methods added to the class repsonsible for user preferences.
94
104
 
95
105
  ## NSIndexPath
96
106
 
97
- Helper methods added to give `NSIndexPath` a bit more or a Ruby
107
+ Helper methods added to give `NSIndexPath` a bit more of a Ruby
98
108
  interface.
99
109
 
100
110
  ## Gestures
@@ -129,20 +139,21 @@ Helper methods to give NSNotificationCenter a Ruby-like interface:
129
139
 
130
140
  ```ruby
131
141
  def viewWillAppear(animated)
132
- notification_center.observe self, UIApplicationWillEnterForegroundNotification do
142
+ @foreground_observer = notification_center.observe UIApplicationWillEnterForegroundNotification do |notification|
133
143
  loadAndRefresh
134
144
  end
135
145
 
136
- notification_center.observe self, ReloadNotification do
146
+ @reload_observer notification_center.observe ReloadNotification do |notification|
137
147
  loadAndRefresh
138
148
  end
139
149
  end
140
150
 
141
151
  def viewWillDisappear(animated)
142
- notification_center.unobserve self
152
+ notification_center.unobserve @foreground_observer
153
+ notification_center.unobserve @reload_observer
143
154
  end
144
155
 
145
156
  def reload
146
157
  notification_center.post ReloadNotification
147
158
  end
148
- ```
159
+ ```
data/Rakefile CHANGED
@@ -3,14 +3,15 @@ $:.unshift("/Library/RubyMotion/lib")
3
3
  require 'motion/project'
4
4
 
5
5
  Motion::Project::App.setup do |app|
6
- app.name = 'MotionLibTestSuite'
6
+ app.name = 'testSuite'
7
+ app.identifier = 'io.bubblewrap.testSuite'
7
8
 
8
9
  app.development do
9
10
  app.files << './lib/tests/test_suite_delegate.rb'
10
11
  app.delegate_class = 'TestSuiteDelegate'
11
12
  end
12
13
 
13
- app.files += Dir.glob('./lib/bubble-wrap/**.rb')
14
+ app.files += Dir.glob('./lib/bubble-wrap/**/*.rb')
14
15
  wrapper_files = app.files.dup
15
16
  pollution_file = Dir.glob('./lib/pollute.rb')[0]
16
17
  app.files << pollution_file
data/lib/bubble-wrap.rb CHANGED
@@ -29,11 +29,11 @@ end
29
29
 
30
30
  Motion::Project::App.setup do |app|
31
31
  wrapper_files = []
32
- Dir.glob(File.join(File.dirname(__FILE__), 'bubble-wrap/*.rb')).each do |file|
32
+ Dir.glob(File.join(File.dirname(__FILE__), 'bubble-wrap/**/*.rb')).each do |file|
33
33
  app.files << file
34
34
  wrapper_files << file
35
35
  end
36
36
  pollution_file = File.expand_path(File.join(File.dirname(__FILE__), 'pollute.rb'))
37
- app.files << pollution_file
37
+ app.files.unshift pollution_file
38
38
  app.files_dependencies pollution_file => wrapper_files
39
39
  end
@@ -1,50 +1,75 @@
1
- # Provides a module to store global states and a persistence layer.
1
+ # Provides a module to store global states
2
2
  #
3
- module App
4
- module_function
3
+ module BubbleWrap
4
+ module App
5
+ module_function
5
6
 
6
- @states = {}
7
+ # Returns the application's document directory path where users might be able to upload content.
8
+ # @return [String] the path to the document directory
9
+ def documents_path
10
+ NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true)[0]
11
+ end
7
12
 
8
- def states
9
- @states
10
- end
13
+ # Returns the application resource path where resource located
14
+ # @return [String] the application main bundle resource path
15
+ def resources_path
16
+ NSBundle.mainBundle.resourcePath
17
+ end
11
18
 
12
- def name
13
- NSBundle.mainBundle.bundleIdentifier
14
- end
19
+ # Returns the default notification center
20
+ # @return [NSNotificationCenter] the default notification center
21
+ def notification_center
22
+ NSNotificationCenter.defaultCenter
23
+ end
15
24
 
16
- # Return application frame
17
- def frame
18
- UIScreen.mainScreen.applicationFrame
19
- end
25
+ def user_cache
26
+ NSUserDefaults.standardUserDefaults
27
+ end
20
28
 
21
- # Application Delegate
22
- def delegate
23
- UIApplication.sharedApplication.delegate
24
- end
29
+ def alert(msg,cancelButtonTitle='OK')
30
+ alert = UIAlertView.alloc.initWithTitle msg,
31
+ message: nil,
32
+ delegate: nil,
33
+ cancelButtonTitle: cancelButtonTitle,
34
+ otherButtonTitles: nil
35
+ alert.show
36
+ alert
37
+ end
38
+
39
+ @states = {}
25
40
 
26
- # Persistence module built on top of NSUserDefaults
27
- module Persistence
28
- def self.app_key
29
- @app_key ||= App.name
41
+ def states
42
+ @states
30
43
  end
31
44
 
32
- def self.[]=(key, value)
33
- defaults = NSUserDefaults.standardUserDefaults
34
- defaults.setObject(value, forKey: storage_key(key.to_s))
35
- defaults.synchronize
45
+ def name
46
+ NSBundle.mainBundle.objectForInfoDictionaryKey 'CFBundleDisplayName'
36
47
  end
37
48
 
38
- def self.[](key)
39
- defaults = NSUserDefaults.standardUserDefaults
40
- defaults.objectForKey storage_key(key.to_s)
49
+ def identifier
50
+ NSBundle.mainBundle.bundleIdentifier
41
51
  end
42
52
 
43
- private
53
+ # Return application frame
54
+ def frame
55
+ UIScreen.mainScreen.applicationFrame
56
+ end
44
57
 
45
- def self.storage_key(key)
46
- app_key + '_' + key.to_s
58
+ # Application Delegate
59
+ def delegate
60
+ UIApplication.sharedApplication.delegate
47
61
  end
48
- end
49
62
 
63
+ # @return [NSLocale] locale of user settings
64
+ def current_locale
65
+ languages = NSLocale.preferredLanguages
66
+ if languages.count > 0
67
+ return NSLocale.alloc.initWithLocaleIdentifier(languages.first)
68
+ else
69
+ return NSLocale.currentLocale
70
+ end
71
+ end
72
+
73
+ end
50
74
  end
75
+ ::App = BubbleWrap::App
@@ -0,0 +1,66 @@
1
+ module BubbleWrap
2
+ module Device
3
+ module_function
4
+
5
+ # Verifies that the device running the app is an iPhone.
6
+ # @return [TrueClass, FalseClass] true will be returned if the device is an iPhone, false otherwise.
7
+ def iphone?(idiom=UIDevice.currentDevice.userInterfaceIdiom)
8
+ idiom == UIUserInterfaceIdiomPhone
9
+ end
10
+
11
+ # Verifies that the device running the app is an iPad.
12
+ # @return [TrueClass, FalseClass] true will be returned if the device is an iPad, false otherwise.
13
+ def ipad?(idiom=UIDevice.currentDevice.userInterfaceIdiom)
14
+ idiom == UIUserInterfaceIdiomPad
15
+ end
16
+
17
+ # Verifies that the device running has a front facing camera.
18
+ # @return [TrueClass, FalseClass] true will be returned if the device has a front facing camera, false otherwise.
19
+ def front_camera?(picker=UIImagePickerController)
20
+ picker.isCameraDeviceAvailable(UIImagePickerControllerCameraDeviceFront)
21
+ end
22
+
23
+ # Verifies that the device running has a rear facing camera.
24
+ # @return [TrueClass, FalseClass] true will be returned if the device has a rear facing camera, false otherwise.
25
+ def rear_camera?(picker=UIImagePickerController)
26
+ picker.isCameraDeviceAvailable(UIImagePickerControllerCameraDeviceRear)
27
+ end
28
+
29
+ def simulator?
30
+ @simulator_state ||= !(UIDevice.currentDevice.model =~ /simulator/i).nil?
31
+ end
32
+
33
+ # Verifies that the device running has a front facing camera.
34
+ # @return [TrueClass, FalseClass] true will be returned if the device has a front facing camera, false otherwise.
35
+ def front_camera?(picker=UIImagePickerController)
36
+ picker.isCameraDeviceAvailable(UIImagePickerControllerCameraDeviceFront)
37
+ end
38
+
39
+ # Verifies that the device running has a rear facing camera.
40
+ # @return [TrueClass, FalseClass] true will be returned if the device has a rear facing camera, false otherwise.
41
+ def rear_camera?(picker=UIImagePickerController)
42
+ picker.isCameraDeviceAvailable(UIImagePickerControllerCameraDeviceRear)
43
+ end
44
+
45
+ def simulator?
46
+ @simulator_state ||= !(UIDevice.currentDevice.model =~ /simulator/i).nil?
47
+ end
48
+
49
+ # Shameless shorthand for accessing BubbleWrap::Screen
50
+ def screen
51
+ BubbleWrap::Device::Screen
52
+ end
53
+
54
+ # Delegates to BubbleWrap::Screen.retina?
55
+ def retina?
56
+ screen.retina?
57
+ end
58
+
59
+ # Delegates to BubbleWrap::Screen.orientation
60
+ def orientation
61
+ screen.orientation
62
+ end
63
+
64
+ end
65
+ end
66
+ ::Device = BubbleWrap::Device
@@ -0,0 +1,69 @@
1
+ module BubbleWrap
2
+ module Device
3
+ module Screen
4
+
5
+ module_function
6
+
7
+ # Certifies that the device running the app has a Retina display
8
+ # @return [TrueClass, FalseClass] true will be returned if the device has a Retina display, false otherwise.
9
+ def retina?(screen=UIScreen.mainScreen)
10
+ if screen.respondsToSelector('displayLinkWithTarget:selector:') && screen.scale == 2.0
11
+ true
12
+ else
13
+ false
14
+ end
15
+ end
16
+
17
+ # Figure out the current physical orientation of the device
18
+ # @return [:portrait, :portrait_upside_down, :landscape_left, :landscape_right, :face_up, :face_down, :unknown]
19
+ def orientation(device_orientation=UIDevice.currentDevice.orientation)
20
+ case device_orientation
21
+ when UIDeviceOrientationPortrait then :portrait
22
+ when UIDeviceOrientationPortraitUpsideDown then :portrait_upside_down
23
+ when UIDeviceOrientationLandscapeLeft then :landscape_left
24
+ when UIDeviceOrientationLandscapeRight then :landscape_right
25
+ when UIDeviceOrientationFaceUp then :face_up
26
+ when UIDeviceOrientationFaceDown then :face_down
27
+ else
28
+ :unknown
29
+ end
30
+ end
31
+
32
+ # The width of the device's screen.
33
+ # The real resolution is dependant on the scale
34
+ # factor (see `retina?`) but the coordinate system
35
+ # is in non-retina pixels. You can get pixel
36
+ # accuracy by using half-coordinates.
37
+ # This is a Float
38
+ def width
39
+ UIScreen.mainScreen.bounds.size.width
40
+ end
41
+
42
+ # The height of the device's screen.
43
+ # The real resolution is dependant on the scale
44
+ # factor (see `retina?`) but the coordinate system
45
+ # is in non-retina pixels. You can get pixel
46
+ # accuracy by using half-coordinates.
47
+ # This is a Float
48
+ def height
49
+ UIScreen.mainScreen.bounds.size.height
50
+ end
51
+
52
+ # The same as `.width` and `.height` but
53
+ # compensating for screen rotation (which
54
+ # can do your head in).
55
+ def widthForOrientation(o=orientation)
56
+ return height if (o == :landscape_left) || (o == :landscape_right)
57
+ width
58
+ end
59
+
60
+ # The same as `.width` and `.height` but
61
+ # compensating for screen rotation (which
62
+ # can do your head in).
63
+ def heightForOrientation(o=orientation)
64
+ return width if (o == :landscape_left) || (o == :landscape_right)
65
+ height
66
+ end
67
+ end
68
+ end
69
+ end
@@ -2,34 +2,35 @@
2
2
 
3
3
  class UIView
4
4
 
5
- def whenTapped(&proc)
6
- addGestureRecognizerHelper(proc, UITapGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
5
+ def whenTapped(enableInteraction=true, &proc)
6
+ addGestureRecognizerHelper(proc, enableInteraction, UITapGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
7
7
  end
8
8
 
9
- def whenPinched(&proc)
10
- addGestureRecognizerHelper(proc, UIPinchGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
9
+ def whenPinched(enableInteraction=true, &proc)
10
+ addGestureRecognizerHelper(proc, enableInteraction, UIPinchGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
11
11
  end
12
12
 
13
- def whenRotated(&proc)
14
- addGestureRecognizerHelper(proc, UIRotationGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
13
+ def whenRotated(enableInteraction=true, &proc)
14
+ addGestureRecognizerHelper(proc, enableInteraction, UIRotationGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
15
15
  end
16
16
 
17
- def whenSwiped(&proc)
18
- addGestureRecognizerHelper(proc, UISwipeGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
17
+ def whenSwiped(enableInteraction=true, &proc)
18
+ addGestureRecognizerHelper(proc, enableInteraction, UISwipeGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
19
19
  end
20
20
 
21
- def whenPanned(&proc)
22
- addGestureRecognizerHelper(proc, UIPanGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
21
+ def whenPanned(enableInteraction=true, &proc)
22
+ addGestureRecognizerHelper(proc, enableInteraction, UIPanGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
23
23
  end
24
24
 
25
- def whenPressed(&proc)
26
- addGestureRecognizerHelper(proc, UILongPressGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
25
+ def whenPressed(enableInteraction=true, &proc)
26
+ addGestureRecognizerHelper(proc, enableInteraction, UILongPressGestureRecognizer.alloc.initWithTarget(proc, action:'call'))
27
27
  end
28
28
 
29
- private
29
+ private
30
30
 
31
31
  # Adds the recognizer and keeps a strong reference to the Proc object.
32
- def addGestureRecognizerHelper(proc, recognizer)
32
+ def addGestureRecognizerHelper(proc, enableInteraction, recognizer)
33
+ setUserInteractionEnabled true if enableInteraction && !isUserInteractionEnabled
33
34
  self.addGestureRecognizer(recognizer)
34
35
  @recognizers = {} unless @recognizers
35
36
  @recognizers["#{proc}"] = proc
@@ -118,6 +118,7 @@ module BubbleWrap
118
118
  @headers = {}
119
119
  headers.each{|k,v| @headers[k] = v.gsub("\n", '\\n') } # escaping LFs
120
120
  end
121
+ @cachePolicy = options.delete(:cache_policy) || NSURLRequestUseProtocolCachePolicy
121
122
  @options = options
122
123
  @response = HTTP::Response.new
123
124
  initiate_request(url)
@@ -152,10 +153,10 @@ module BubbleWrap
152
153
  url_string = "#{url_string}?#{@payload}" if @method == "GET"
153
154
  end
154
155
 
155
- p "BubbleWrap::HTTP building a NSRequest for #{url_string}"# if SETTINGS[:debug]
156
- @url = NSURL.URLWithString(url_string)
156
+ p "BubbleWrap::HTTP building a NSRequest for #{url_string}" if SETTINGS[:debug]
157
+ @url = NSURL.URLWithString(url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding)
157
158
  @request = NSMutableURLRequest.requestWithURL(@url,
158
- cachePolicy:NSURLRequestUseProtocolCachePolicy,
159
+ cachePolicy:@cachePolicy,
159
160
  timeoutInterval:@timeout)
160
161
  @request.setHTTPMethod @method
161
162
  @request.setAllHTTPHeaderFields(@headers) if @headers
@@ -168,7 +169,7 @@ module BubbleWrap
168
169
 
169
170
  # NSHTTPCookieStorage.sharedHTTPCookieStorage
170
171
 
171
- @connection = NSURLConnection.connectionWithRequest(request, delegate:self)
172
+ @connection = create_connection(request, self)
172
173
  @request.instance_variable_set("@done_loading", false)
173
174
  def @request.done_loading; @done_loading; end
174
175
  def @request.done_loading!; @done_loading = true; end
@@ -178,7 +179,6 @@ module BubbleWrap
178
179
  @status_code = response.statusCode
179
180
  @response_headers = response.allHeaderFields
180
181
  @response_size = response.expectedContentLength.to_f
181
- # p "HTTP status code: #{@status_code}, content length: #{@response_size}, headers: #{@response_headers}" if SETTINGS[:debug]
182
182
  end
183
183
 
184
184
  # This delegate method get called every time a chunk of data is being received
@@ -188,49 +188,44 @@ module BubbleWrap
188
188
  end
189
189
 
190
190
  def connection(connection, willSendRequest:request, redirectResponse:redirect_response)
191
- p "HTTP redirected #{request.description}" #if SETTINGS[:debug]
191
+ p "HTTP redirected #{request.description}" if SETTINGS[:debug]
192
192
  new_request = request.mutableCopy
193
193
  # new_request.setValue(@credentials.inspect, forHTTPHeaderField:'Authorization') # disabled while we figure this one out
194
194
  new_request.setAllHTTPHeaderFields(@headers) if @headers
195
195
  @connection.cancel
196
- @connection = NSURLConnection.connectionWithRequest(new_request, delegate:self)
196
+ @connection = create_connection(new_request, self)
197
197
  new_request
198
198
  end
199
199
 
200
200
  def connection(connection, didFailWithError: error)
201
201
  UIApplication.sharedApplication.networkActivityIndicatorVisible = false
202
202
  @request.done_loading!
203
- NSLog"HTTP Connection failed #{error.localizedDescription}"
203
+ NSLog"HTTP Connection failed #{error.localizedDescription}" if SETTINGS[:debug]
204
204
  @response.error_message = error.localizedDescription
205
- if @delegator.respond_to?(:call)
206
- @delegator.call( @response, self )
207
- end
205
+ call_delegator_with_response
208
206
  end
209
207
 
208
+
210
209
  # The transfer is done and everything went well
211
210
  def connectionDidFinishLoading(connection)
212
211
  UIApplication.sharedApplication.networkActivityIndicatorVisible = false
213
212
  @request.done_loading!
214
-
215
213
  # copy the data in a local var that we will attach to the response object
216
214
  response_body = NSData.dataWithData(@received_data) if @received_data
217
215
  @response.update(status_code: status_code, body: response_body, headers: response_headers, url: @url)
218
- # Don't reset the received data since this method can be called multiple times if the headers can report the wrong length.
219
- # @received_data = nil
220
- if @delegator.respond_to?(:call)
221
- @delegator.call( @response, self )
222
- end
216
+
217
+ call_delegator_with_response
223
218
  end
224
219
 
225
220
  def connection(connection, didReceiveAuthenticationChallenge:challenge)
226
- # p "HTTP auth required" if SETTINGS[:debug]
221
+
227
222
  if (challenge.previousFailureCount == 0)
228
223
  # by default we are keeping the credential for the entire session
229
224
  # Eventually, it would be good to let the user pick one of the 3 possible credential persistence options:
230
225
  # NSURLCredentialPersistenceNone,
231
226
  # NSURLCredentialPersistenceForSession,
232
227
  # NSURLCredentialPersistencePermanent
233
- p "auth challenged, answered with credentials: #{credentials.inspect}"
228
+ p "auth challenged, answered with credentials: #{credentials.inspect}" if SETTINGS[:debug]
234
229
  new_credential = NSURLCredential.credentialWithUser(credentials[:username], password:credentials[:password], persistence:NSURLCredentialPersistenceForSession)
235
230
  challenge.sender.useCredential(new_credential, forAuthenticationChallenge:challenge)
236
231
  else
@@ -238,6 +233,17 @@ module BubbleWrap
238
233
  p 'Auth Failed :('
239
234
  end
240
235
  end
236
+
237
+ def call_delegator_with_response
238
+ if @delegator.respond_to?(:call)
239
+ @delegator.call( @response, self )
240
+ end
241
+ end
242
+
243
+ # This is a temporary method used for mocking.
244
+ def create_connection(request, delegate)
245
+ NSURLConnection.connectionWithRequest(request, delegate:delegate)
246
+ end
241
247
  end
242
248
  end
243
249
  end