bubble-wrap 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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