bluepotion 0.1.2 → 0.1.3

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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/lib/project/blue_potion_net.rb +105 -0
  3. data/lib/project/ext/object.rb +2 -0
  4. data/lib/project/potion.rb +19 -3
  5. data/lib/project/potion_dialog/potion_dialog.rb +46 -0
  6. data/lib/project/pro_motion/{pm_activity.rb → activities/pm_activity.rb} +18 -0
  7. data/lib/project/pro_motion/activities/pm_home_activity.rb +14 -0
  8. data/lib/project/pro_motion/activities/pm_navigation_activity.rb +83 -0
  9. data/lib/project/pro_motion/{pm_single_fragment_activity.rb → activities/pm_single_fragment_activity.rb} +14 -3
  10. data/lib/project/pro_motion/adapters/pm_base_adapter.rb +75 -0
  11. data/lib/project/pro_motion/adapters/pm_cursor_adapter.rb +44 -0
  12. data/lib/project/pro_motion/fragments/pm_list_screen.rb +191 -0
  13. data/lib/project/pro_motion/{pm_screen.rb → fragments/pm_screen.rb} +31 -6
  14. data/lib/project/pro_motion/{pm_screen_module.rb → fragments/pm_screen_module.rb} +72 -20
  15. data/lib/project/pro_motion/pm_application.rb +15 -4
  16. data/lib/project/pro_motion/{pm_hash_bundle.rb → support/pm_hash_bundle.rb} +4 -0
  17. data/lib/project/ruby_motion_query/rmq/base.rb +1 -1
  18. data/lib/project/ruby_motion_query/rmq/data.rb +2 -36
  19. data/lib/project/ruby_motion_query/rmq/subviews.rb +30 -0
  20. data/lib/project/ruby_motion_query/rmq_color.rb +1 -1
  21. data/lib/project/ruby_motion_query/rmq_resource.rb +26 -0
  22. data/lib/project/version.rb +1 -1
  23. data/lib/project/volley_wrap/http_result.rb +98 -0
  24. data/lib/project/volley_wrap/request.rb +98 -0
  25. data/lib/project/volley_wrap/response_listener.rb +49 -0
  26. data/lib/project/volley_wrap/session_client.rb +72 -0
  27. metadata +19 -8
  28. data/lib/project/pro_motion/pm_home_activity.rb +0 -16
@@ -6,7 +6,11 @@
6
6
 
7
7
  attr_accessor :view
8
8
 
9
- def onAttach(activity); super; on_attach(activity); end
9
+ def onAttach(activity)
10
+ super
11
+ activity.on_fragment_attached(self) if activity.respond_to?(:on_fragment_attached)
12
+ on_attach(activity)
13
+ end
10
14
  def on_attach(activity); end
11
15
 
12
16
  def onCreate(bundle); super; on_create(bundle); end
@@ -18,7 +22,8 @@
18
22
  if @xml_resource = self.class.xml_resource
19
23
  @view = inflater.inflate(r(:layout, @xml_resource), parent, false)
20
24
  else
21
- @view = load_view
25
+ v = load_view
26
+ @view ||= v
22
27
  @view.setId Potion::ViewIdGenerator.generate
23
28
  end
24
29
 
@@ -50,9 +55,7 @@
50
55
 
51
56
  build_and_tag_xml_views
52
57
 
53
- self.action_bar.title = self.class.bars_title
54
- self.activity.title = self.class.bars_title
55
-
58
+ set_title
56
59
  on_load
57
60
  on_activity_created
58
61
  end
@@ -80,9 +83,31 @@
80
83
  def onDestroy; super; on_destroy; end
81
84
  def on_destroy; end
82
85
 
83
- def onDetach; super; on_detach; end
86
+ def onDetach
87
+ super
88
+ on_detach
89
+ self.activity.on_fragment_detached(self) if self.activity.respond_to?(:on_fragment_detached)
90
+ end
84
91
  def on_detach; end
85
92
 
93
+ def set_title
94
+ self.title = self.class.bars_title
95
+ end
96
+
97
+ def title
98
+ @title
99
+ end
100
+ def title=(value)
101
+ @title = value
102
+
103
+ if a = self.activity
104
+ if a_bar = self.action_bar
105
+ a_bar.title = value
106
+ end
107
+ a.title = value
108
+ end
109
+ end
110
+
86
111
  private
87
112
 
88
113
  def build_and_tag_xml_views
@@ -93,8 +93,21 @@
93
93
  self.rmq.append(view_or_class, style, opts).get
94
94
  end
95
95
 
96
- # TODO add create and build
96
+ def create(view_or_class, style=nil, opts={})
97
+ self.rmq.create(view_or_class, style, opts)
98
+ end
99
+
100
+ def create!(view_or_class, style=nil, opts={})
101
+ self.rmq.create(view_or_class, style, opts).get
102
+ end
97
103
 
104
+ def build(view_or_class, style=nil, opts={})
105
+ self.rmq.build(view_or_class, style, opts)
106
+ end
107
+
108
+ def build!(view_or_class, style=nil, opts={})
109
+ self.rmq.build(view_or_class, style, opts).get
110
+ end
98
111
 
99
112
  # temporary stand-in for Java's R class
100
113
  def r(resource_type, resource_name)
@@ -108,15 +121,27 @@
108
121
 
109
122
  def open(screen_class, options={})
110
123
  mp "ScreenModule open", debugging_only: true
111
- activity_class = options.delete(:activity) || PMSingleFragmentActivity
112
124
 
113
- # TODO: replace the fragment in the activity when possible
114
- # replace the fragment if we can; otherwise launch a new activity
115
- # we're taking a conservative approach for now - eventually we'll want to allow
116
- # replacing fragments for any kind of activity, but I'm not sure of the best way
117
- # to implement that yet
118
- intent = Android::Content::Intent.new(self.activity, activity_class)
119
- intent.putExtra PMSingleFragmentActivity::EXTRA_FRAGMENT_CLASS, screen_class.to_s
125
+ if !options[:activity] && self.activity.respond_to?(:open_fragment)
126
+ if screen_class.respond_to?(:new)
127
+ screen = screen_class.new
128
+ else
129
+ screen = screen_class
130
+ end
131
+ self.activity.open_fragment screen, options
132
+ else
133
+ open_modal(screen_class, options)
134
+ end
135
+ end
136
+
137
+ def open_modal(screen_class, options)
138
+ activity_class = options.delete(:activity) || PMNavigationActivity
139
+ activity_class = PMNavigationActivity if activity_class == :nav
140
+ activity_class = PMSingleFragmentActivity if activity_class == :single
141
+
142
+ intent = Potion::Intent.new(self.activity, activity_class)
143
+ intent.putExtra PMActivity::EXTRA_FRAGMENT_CLASS, screen_class.to_s
144
+ intent.setFlags(Potion::Intent::FLAG_ACTIVITY_CLEAR_TOP) if options.delete(:close)
120
145
 
121
146
  if extras = options.delete(:extras)
122
147
  extras.keys.each do |key, value|
@@ -128,18 +153,34 @@
128
153
  unless options.blank?
129
154
  # The rest of the options are screen accessors, we use fragment arguments for this
130
155
  hash_bundle = PMHashBundle.from_hash(options)
131
- intent.putExtra PMSingleFragmentActivity::EXTRA_FRAGMENT_ARGUMENTS, hash_bundle.to_bundle
156
+ intent.putExtra PMActivity::EXTRA_FRAGMENT_ARGUMENTS, hash_bundle.to_bundle
132
157
  end
133
158
 
134
159
  self.activity.startActivity intent
135
160
  end
136
161
 
137
162
  def close(options={})
138
- self.activity.finish
163
+ # Hang onto an activity reference, since we lose the activity
164
+ act = self.activity
165
+
166
+ if options[:activity] && options[:to_screen]
167
+ # Closing to particular activity
168
+ open options[:to_screen], activity: options[:activity], close: true
169
+ elsif options[:to_screen]
170
+ # Closing to particular fragment
171
+ while act.fragment && !act.fragment.is_a?(options[:to_screen])
172
+ act.close_fragment
173
+ act.finish unless act.fragment
174
+ end
175
+ else
176
+ # Closing current screen or activity if no screens left
177
+ act.close_fragment if act.fragment
178
+ act.finish unless act.fragment
179
+ end
139
180
  end
140
181
 
141
182
  def start_activity(activity_class)
142
- intent = Android::Content::Intent.new(self.activity, activity_class)
183
+ intent = Potion::Intent.new(self.activity, activity_class)
143
184
  #intent.putExtra("key", value); # Optional parameters
144
185
  self.activity.startActivity(intent)
145
186
  end
@@ -158,13 +199,10 @@
158
199
  input_manager.hideSoftInputFromWindow(view.getWindowToken(), 0);
159
200
  end
160
201
 
161
-
162
- def activity
163
- getActivity()
164
- end
165
-
166
202
  def action_bar
167
- activity.getActionBar()
203
+ if a = activity
204
+ a.getActionBar
205
+ end
168
206
  end
169
207
 
170
208
  def menu
@@ -173,6 +211,7 @@
173
211
 
174
212
  # Example: add_action_bar_button(title: "My text", show: :if_room)
175
213
  def add_action_bar_button(options={})
214
+ @action_bar ||= { button_actions: {} }
176
215
  unless menu
177
216
  mp "#{self.inspect}#add_action_bar_button: No menu set up yet."
178
217
  return
@@ -187,11 +226,24 @@
187
226
  show_as_action = 4 if options[:show] == :with_text
188
227
  show_as_action = 8 if options[:show] == :collapse
189
228
 
190
- btn = self.activity.menu.add(options.fetch(:group, 0), options.fetch(:item_id, 0), options.fetch(:order, 0), options.fetch(:title, "Untitled"))
229
+ btn = self.activity.menu.add(options.fetch(:group, 0), options.fetch(:item_id, @action_bar[:current_id] || 0), options.fetch(:order, 0), options.fetch(:title, ""))
191
230
  btn.setShowAsAction(show_as_action) if show_as_action
192
- btn.setIcon(options[:icon]) if options[:icon]
231
+ btn.setIcon(image.resource(options[:icon].to_s)) if options[:icon]
232
+ @action_bar[:button_actions][btn.getItemId] = options[:action] if options[:action]
233
+ @action_bar[:current_id] = btn.getItemId + 1
193
234
  btn
194
235
  end
195
236
 
237
+ def on_options_item_selected(item)
238
+ return unless @action_bar
239
+ return unless method_name = @action_bar[:button_actions][item.getItemId]
240
+ if respond_to?(method_name)
241
+ send(method_name)
242
+ else
243
+ mp "#{self.inspect} No method #{method_name.inspect} implemented for this screen."
244
+ true
245
+ end
246
+ end
247
+
196
248
  end
197
249
  #end
@@ -43,11 +43,10 @@
43
43
  end
44
44
  end
45
45
 
46
+ # Typically you don't use this, use `find.screen` instead, TODO, probably should remove this
46
47
  def current_screen
47
- if @current_activity && (ca = @current_activity)
48
- if ca.is_a?(PMSingleFragmentActivity)
49
- ca.fragment
50
- end
48
+ if @current_activity && @current_activity.respond_to?(:fragment)
49
+ @current_activity.fragment
51
50
  end
52
51
  end
53
52
 
@@ -78,6 +77,18 @@
78
77
  environment == :development
79
78
  end
80
79
 
80
+ def resource
81
+ RMQResource
82
+ end
83
+
84
+ def net
85
+ BluePotionNet
86
+ end
87
+
88
+ def async(options={}, &block)
89
+ MotionAsync.async(options, &block)
90
+ end
91
+
81
92
  class << self
82
93
  attr_accessor :current_application, :home_screen_class
83
94
 
@@ -30,6 +30,8 @@ class PMHashBundle
30
30
  value = case value_type
31
31
  when "com.rubymotion.String"
32
32
  bundle.getString(key)
33
+ when "com.rubymotion.Symbol"
34
+ bundle.getString(key).to_sym
33
35
  when "java.lang.Integer"
34
36
  bundle.getInt(key)
35
37
  when "java.lang.Double"
@@ -64,6 +66,8 @@ class PMHashBundle
64
66
  case value_type
65
67
  when "com.rubymotion.String"
66
68
  bundle.putString(key, value)
69
+ when "com.rubymotion.Symbol"
70
+ bundle.putString(key, value.to_s)
67
71
  when "java.lang.Integer"
68
72
  bundle.putInt(key, value)
69
73
  when "java.lang.Double"
@@ -8,7 +8,7 @@ class RMQ
8
8
  if value.is_a?(Potion::Activity)
9
9
  @originated_from = value
10
10
  @activity = value
11
- elsif value.is_a?(PMScreen)
11
+ elsif value.is_a?(PMScreen) || value.is_a?(PMListScreen)
12
12
  @originated_from = value
13
13
  elsif value.is_a?(Potion::View)
14
14
  @originated_from = value
@@ -3,25 +3,8 @@ class RMQ
3
3
  if new_data != :rmq_not_provided
4
4
  selected.each do |view|
5
5
  case view
6
- #when UILabel then view.setText new_data # set is faster than =
7
- #when UIButton then view.setTitle(new_data, forState: UIControlStateNormal)
8
- #when UIImageView then view.image = new_data
9
- #when UITableView then
10
- #when UISwitch then view.setOn(new_data)
11
- #when UIDatePicker then
12
- #when UISegmentedControl then
13
- #when UIRefreshControl then
14
- #when UIPageControl then
15
- #when UISlider then
16
- #when UIStepper then
17
- #when UITabBar then
18
- #when UITableViewCell then
6
+ when Potion::EditText then view.text = new_data.to_s.toString
19
7
  when Potion::TextView then view.text = new_data
20
- #when UITextField then view.text = new_data
21
- #when UINavigationBar then
22
- #when UIScrollView then
23
- #when UIProgressView then view.setProgress(new_data, animated: true)
24
-
25
8
  # TODO, finish
26
9
  end
27
10
  end
@@ -30,25 +13,8 @@ class RMQ
30
13
  else
31
14
  out = selected.map do |view|
32
15
  case view
33
- #when UILabel then view.text
34
- #when UIButton then view.titleForState(UIControlStateNormal)
35
- #when UIImageView then view.image
36
- #when UITableView then
37
- #when UISwitch then
38
- #when UIDatePicker then
39
- #when UISegmentedControl then
40
- #when UIRefreshControl then
41
- #when UIPageControl then
42
- #when UISlider then
43
- #when UIStepper then
44
- #when UITabBar then
45
- #when UITableViewCell then
16
+ when Potion::EditText then view.text.toString.to_s
46
17
  when Potion::TextView then view.text
47
- #when UITextField then view.text
48
- #when UINavigationBar then
49
- #when UIScrollView then
50
- #when UIProgressView then view.progress
51
-
52
18
  # TODO, finish
53
19
  end
54
20
  end
@@ -6,9 +6,17 @@ class RMQ
6
6
  created = false
7
7
  appended = false
8
8
  built = false
9
+ tag_xml_layout = false
9
10
 
10
11
  if view_or_class.is_a?(Potion::View)
11
12
  new_view = view_or_class
13
+ elsif view_or_class.is_a?(Symbol) # Inflate from xml
14
+ created = true
15
+ layout = RMQResource.layout(view_or_class)
16
+
17
+ inflater = Potion::LayoutInflater.from(self.activity)
18
+ new_view = inflater.inflate(layout, nil)
19
+ tag_xml_layout = true
12
20
  else
13
21
  created = true
14
22
  new_view = view_or_class.new(RMQ.app.context)
@@ -48,6 +56,8 @@ class RMQ
48
56
  if self.stylesheet
49
57
  apply_style_to_view(new_view, opts[:style]) if opts[:style]
50
58
  end
59
+
60
+ tag_all_from_resource_entry_name(new_view) if tag_xml_layout
51
61
  end
52
62
 
53
63
  viewq = RMQ.create_with_array_and_selectors(subviews_added, selectors, @originated_from, self)
@@ -57,6 +67,26 @@ class RMQ
57
67
  end
58
68
  alias :insert :add_subview
59
69
 
70
+ def tag_all_from_resource_entry_name(view)
71
+ view.rmq.find.each do |view|
72
+ if ren = view.resource_entry_name
73
+ view.rmq_data.tag(ren.to_sym)
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+ # Removes the selected views from their parent's (superview) subview array
80
+ #
81
+ # @example
82
+ # rmq(a_view, another_view).remove
83
+ #
84
+ # @return [RMQ]
85
+ def remove
86
+ selected.each { |view| view.parent.removeView(view) }
87
+ self
88
+ end
89
+
60
90
  def append(view_or_class, style=nil, opts={}, dummy=nil) # <- dummy is to get around RM bug)
61
91
  opts[:style] = style
62
92
  #opts[:block] = block if block
@@ -92,7 +92,7 @@ end
92
92
  class RMQColorFactory
93
93
 
94
94
  class << self
95
- def build(params)
95
+ def build(params, dummy=nil) # Dummy works around RM bug
96
96
  return RMQColor if params.empty?
97
97
  return from_rgba(*params) if params.count > 1
98
98
 
@@ -0,0 +1,26 @@
1
+ class RMQ
2
+ # @return [RMQResource]
3
+ def self.resource
4
+ RMQResource
5
+ end
6
+
7
+ # @return [RMQResource]
8
+ def resource
9
+ RMQResource
10
+ end
11
+ end
12
+
13
+ class RMQResource
14
+ class << self
15
+ def find(resource_type, resource_name)
16
+ application = PMApplication.current_application
17
+ application.resources.getIdentifier(resource_name.to_s,
18
+ resource_type.to_s,
19
+ application.package_name)
20
+ end
21
+
22
+ def layout(name)
23
+ self.find("layout", name)
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module BluePotion
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -0,0 +1,98 @@
1
+ module VW
2
+ class HTTPResult
3
+ attr_accessor :object, :error, :response, :request_url, :request_params, :request_method
4
+
5
+ def initialize(response, response_object, error)
6
+ @response = response
7
+ @object = response_object
8
+ @error = error
9
+ end
10
+
11
+ def status_code
12
+ @response.statusCode if @response
13
+ end
14
+
15
+ def not_modified?
16
+ @response.notModified if @response
17
+ end
18
+
19
+ def body
20
+ @object.to_s if @object
21
+ end
22
+
23
+ def method_description
24
+ case @request_method
25
+ when 0
26
+ "GET"
27
+ when 1
28
+ "POST"
29
+ when 2
30
+ "PUT"
31
+ when 3
32
+ "DELETE"
33
+ else
34
+ "Unknown"
35
+ end
36
+ end
37
+
38
+ def headers
39
+ if @response
40
+ @_headers ||= @response.headers.inject({}){|h, entry_set| h[entry_set[0]] = entry_set[1] ; h }
41
+ end
42
+ end
43
+
44
+ def success?
45
+ !failure?
46
+ end
47
+
48
+ def failure?
49
+ !!error
50
+ end
51
+
52
+ def inspect
53
+ "<VW::HTTPResult:#{self.object_id} #{@request_url}>"
54
+ end
55
+
56
+ def to_s
57
+ header_string = if (h = headers)
58
+ h.map{|k,v| " #{k} = #{v}"}.join("\n")
59
+ else
60
+ "none"
61
+ end
62
+
63
+ #mp @request_params.class.name
64
+ params_string = if @request_params
65
+ #@request_params.class.name
66
+ @request_params.map{|k,v| " #{k} = #{v}"}.join("\n")
67
+ else
68
+ "none"
69
+ end
70
+
71
+ %(
72
+
73
+ Request -------------------------
74
+
75
+ URL: #{@request_url}
76
+ Method: #{method_description}
77
+ Params:
78
+ #{params_string}
79
+
80
+ Response -------------------------
81
+
82
+ Status code: #{status_code}
83
+ Not modified?: #{not_modified?}
84
+ Success: #{success?}
85
+
86
+ Error: #{error.toString if error}
87
+
88
+ Headers:
89
+ #{header_string}
90
+
91
+ Body:
92
+ #{body}
93
+ \n
94
+ )
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,98 @@
1
+ module VW
2
+ class Request < Com::Android::Volley::Toolbox::StringRequest
3
+ # part of this class had to be implemented as a Java extension to work around
4
+ # what appears to be a RM bug. See request.java
5
+
6
+ VOLLEY_GET = 0
7
+ VOLLEY_POST = 1
8
+ VOLLEY_PUT = 2
9
+ VOLLEY_DELETE = 3
10
+
11
+ def self.get_request(url, listener)
12
+ set_request_for_listener VOLLEY_GET, url, nil, listener
13
+ Request.new(VOLLEY_GET, url, listener, listener).tap do |req|
14
+ req.setRetryPolicy(retry_policy)
15
+ req.listener = listener
16
+ end
17
+ end
18
+
19
+ def self.post_request(url, params, listener)
20
+ request_with_params(VOLLEY_POST, url, params, listener).tap do |req|
21
+ req.listener = listener
22
+ end
23
+ end
24
+
25
+ def self.put_request(url, params, listener)
26
+ request_with_params(VOLLEY_PUT, url, params, listener).tap do |req|
27
+ req.listener = listener
28
+ end
29
+ end
30
+
31
+ def self.delete_request(url, params, listener)
32
+ request_with_params(VOLLEY_DELETE, url, params, listener).tap do |req|
33
+ req.listener = listener
34
+ end
35
+ end
36
+
37
+ def listener=(value)
38
+ @listener = value
39
+ end
40
+
41
+ def self.retry_policy
42
+ Com::Android::Volley::DefaultRetryPolicy.new(10000, 3, 1)
43
+ end
44
+
45
+ def headers=(headers)
46
+ # call into the method defined in the .java file
47
+ setHeaders(headers)
48
+ end
49
+
50
+ def parseNetworkResponse(network_response)
51
+ @listener.network_response = network_response if @listener
52
+ super
53
+ end
54
+
55
+ def self.set_request_for_listener(method, url, params, listener)
56
+ # There probably is a much better way then passing all these around like this
57
+ listener.request_url = url
58
+ listener.request_params = params
59
+ listener.request_method = method
60
+ end
61
+
62
+ private
63
+
64
+ # this is to maintain compatibility with AFMotion - somewhere (possibly in AFNetworking?) the
65
+ # keys get flattened out like so:
66
+ #
67
+ # { user: { email: "x@x.com", password: "pass" } }
68
+ # becomes
69
+ # { "user[email]" => "x@x.com", "user[password]" => "pass" }
70
+ #
71
+ # This is not a robust implementation of this, but will serve our needs for now
72
+ def self.prepare_params(params)
73
+ new_params = {}
74
+ params.keys.each do |key|
75
+ if params[key].is_a?(Hash)
76
+ params[key].keys.each do |inner_key|
77
+ new_params["#{key}[#{inner_key}]"] = params[key][inner_key].to_s
78
+ end
79
+ else
80
+ new_params[key.to_s] = params[key].to_s
81
+ end
82
+ end
83
+ new_params
84
+ end
85
+
86
+ def self.request_with_params(method, url, params, listener)
87
+ set_request_for_listener method, url, params, listener
88
+
89
+ Request.new(method, url, listener, listener).tap do |req|
90
+ req.setRetryPolicy(retry_policy)
91
+ params = prepare_params(params)
92
+ req.setParams(params)
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ end
@@ -0,0 +1,49 @@
1
+ module VW
2
+ class ResponseListener
3
+ attr_accessor :serializer, :callback, :network_response, :request_url, :request_params, :request_method
4
+
5
+ def initialize(serializer, &block)
6
+ @serializer = serializer
7
+ @callback = block
8
+ end
9
+
10
+ def onResponse(response)
11
+ response_object = expect_json? ? Moran.parse(response.to_s) : response
12
+ create_result(@network_response, response_object, nil)
13
+ end
14
+
15
+ def onErrorResponse(error)
16
+ if network_response = error.networkResponse
17
+ data = network_response.data
18
+ end
19
+ create_result(network_response, data, error.toString)
20
+ end
21
+
22
+ private
23
+
24
+ def expect_json?
25
+ serializer == :json
26
+ end
27
+
28
+ def create_result(response, response_object, error)
29
+ request = HTTPResult.new(response, response_object, error)
30
+ request.request_url = @request_url
31
+ request.request_params = @request_params
32
+ request.request_method = @request_method
33
+
34
+ if VW::SessionClient.debug
35
+ mp request.to_s
36
+ end
37
+ callback.call request
38
+ end
39
+
40
+ def dump_network_error(error)
41
+ puts error.toString
42
+ if response = error.networkResponse
43
+ puts response.statusCode
44
+ puts response.headers
45
+ puts response.data
46
+ end
47
+ end
48
+ end
49
+ end