bubble-wrap 1.3.0 → 1.4.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.
- 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
@@ -8,23 +8,27 @@ module BubbleWrap
|
|
8
8
|
|
9
9
|
# Create a new timer that fires after a given number of seconds
|
10
10
|
def initialize(interval, *args, &blk)
|
11
|
-
options = args.last.is_a?(Hash) ? args.last : {}
|
12
11
|
callback = args.first.respond_to?(:call) ? args.first : blk
|
13
12
|
raise ArgumentError, "No callback or block supplied to periodic timer" unless callback
|
14
|
-
|
13
|
+
|
14
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
15
|
+
if options[:common_modes]
|
16
|
+
NSLog "[DEPRECATED - Option :common_modes] a Run Loop Mode is no longer needed."
|
17
|
+
end
|
18
|
+
|
15
19
|
self.interval = interval
|
16
|
-
|
20
|
+
|
21
|
+
leeway = interval
|
22
|
+
queue = Dispatch::Queue.current
|
23
|
+
@timer = Dispatch::Source.timer(leeway, interval, 0.0, queue) do
|
17
24
|
callback.call
|
18
25
|
trigger(:fired)
|
19
|
-
|
20
|
-
@timer = NSTimer.timerWithTimeInterval(interval, target: fire, selector: 'call:', userInfo: nil, repeats: true)
|
21
|
-
runloop_mode = options[:common_modes] ? NSRunLoopCommonModes : NSDefaultRunLoopMode
|
22
|
-
NSRunLoop.currentRunLoop.addTimer(@timer, forMode: runloop_mode)
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
# Cancel the timer
|
26
30
|
def cancel
|
27
|
-
@timer.
|
31
|
+
@timer.cancel!
|
28
32
|
trigger(:cancelled)
|
29
33
|
end
|
30
34
|
|
data/motion/reactor/timer.rb
CHANGED
@@ -5,17 +5,21 @@ module BubbleWrap
|
|
5
5
|
include Eventable
|
6
6
|
|
7
7
|
# Create a new timer that fires after a given number of seconds
|
8
|
-
def initialize(
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
def initialize(leeway, callback=nil, &blk)
|
9
|
+
queue = Dispatch::Queue.current
|
10
|
+
@timer = Dispatch::Source.timer(leeway, Dispatch::TIME_FOREVER, 0.0, queue) do |src|
|
11
|
+
begin
|
12
|
+
(callback || blk).call
|
13
|
+
trigger(:fired)
|
14
|
+
ensure
|
15
|
+
src.cancel!
|
16
|
+
end
|
17
|
+
end
|
14
18
|
end
|
15
19
|
|
16
20
|
# Cancel the timer
|
17
21
|
def cancel
|
18
|
-
@timer.
|
22
|
+
@timer.cancel! if @timer
|
19
23
|
trigger(:cancelled)
|
20
24
|
true
|
21
25
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BubbleWrap
|
2
|
+
module SMS
|
3
|
+
class Result
|
4
|
+
attr_accessor :result
|
5
|
+
|
6
|
+
def initialize(result)
|
7
|
+
self.result = result
|
8
|
+
end
|
9
|
+
|
10
|
+
def sent?
|
11
|
+
self.result == MessageComposeResultSent
|
12
|
+
end
|
13
|
+
|
14
|
+
def canceled?
|
15
|
+
self.result == MessageComposeResultCancelled
|
16
|
+
end
|
17
|
+
|
18
|
+
def failed?
|
19
|
+
self.result == MessageComposeResultFailed
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/motion/sms/sms.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module BubbleWrap
|
2
|
+
module SMS
|
3
|
+
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# Base method to create your in-app mail
|
7
|
+
# ---------------------------------------
|
8
|
+
# EX
|
9
|
+
# BW::SMS.compose (
|
10
|
+
# {
|
11
|
+
# delegate: self, # optional, will use root view controller by default
|
12
|
+
# to: [ "1(234)567-8910" ],
|
13
|
+
# message: "This is my message. It isn't very long.",
|
14
|
+
# animated: false
|
15
|
+
# }) {|result, error|
|
16
|
+
# result.sent? # => boolean
|
17
|
+
# result.canceled? # => boolean
|
18
|
+
# result.failed? # => boolean
|
19
|
+
# error # => NSError
|
20
|
+
# }
|
21
|
+
|
22
|
+
def compose(options={}, &callback)
|
23
|
+
@delegate = options[:delegate] || App.window.rootViewController
|
24
|
+
@callback = callback
|
25
|
+
@message_controller = create_message_controller(options)
|
26
|
+
@message_is_animated = options[:animated] == false ? false : true
|
27
|
+
@delegate.presentModalViewController(@message_controller, animated: @message_is_animated)
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_message_controller(options={})
|
31
|
+
message_controller = MFMessageComposeViewController.alloc.init
|
32
|
+
message_controller.messageComposeDelegate = self
|
33
|
+
message_controller.body = options[:message]
|
34
|
+
message_controller.recipients = Array(options[:to])
|
35
|
+
message_controller
|
36
|
+
end
|
37
|
+
|
38
|
+
# Event when the MFMessageComposeViewController is closed
|
39
|
+
# -------------------------------------------------------------
|
40
|
+
# the callback is fired if it was present in the constructor
|
41
|
+
|
42
|
+
def messageComposeViewController(controller, didFinishWithResult: result)
|
43
|
+
@delegate.dismissModalViewControllerAnimated(@message_is_animated)
|
44
|
+
@callback.call Result.new(result) if @callback
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/motion/ui/pollute.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
# Please, no more! It'll hurt BubbleWrap's compatibility with other libraries.
|
1
2
|
[
|
2
|
-
[UIControl,
|
3
|
+
[UIControl, BW::UIControlWrapper],
|
4
|
+
[UIView, BW::UIViewWrapper],
|
5
|
+
[UIViewController, BW::UIViewControllerWrapper],
|
3
6
|
].each do |base, wrapper|
|
4
7
|
base.send(:include, wrapper)
|
5
8
|
end
|
data/motion/ui/ui_alert_view.rb
CHANGED
@@ -38,23 +38,30 @@ module BW
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def default(options = {}, &block)
|
41
|
-
options = {
|
42
|
-
|
41
|
+
options = {buttons: "OK"}.merge!(options)
|
42
|
+
options[:style] = :default
|
43
|
+
new(options, &block)
|
43
44
|
end
|
44
45
|
|
45
46
|
def plain_text_input(options = {}, &block)
|
46
|
-
options = {
|
47
|
-
|
47
|
+
options = {buttons: ["Cancel", "OK"],
|
48
|
+
cancel_button_index: 0}.merge!(options)
|
49
|
+
options[:style] = :plain_text_input
|
50
|
+
new(options, &block)
|
48
51
|
end
|
49
52
|
|
50
53
|
def secure_text_input(options = {}, &block)
|
51
|
-
options = {
|
52
|
-
|
54
|
+
options = {buttons: ["Cancel", "OK"],
|
55
|
+
cancel_button_index: 0}.merge!(options)
|
56
|
+
options[:style] = :secure_text_input
|
57
|
+
new(options, &block)
|
53
58
|
end
|
54
59
|
|
55
60
|
def login_and_password_input(options = {}, &block)
|
56
|
-
options = {
|
57
|
-
|
61
|
+
options = {buttons: ["Cancel", "Log in"],
|
62
|
+
cancel_button_index: 0}.merge!(options)
|
63
|
+
options[:style] = :login_and_password_input
|
64
|
+
new(options, &block)
|
58
65
|
end
|
59
66
|
end
|
60
67
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module BubbleWrap
|
2
|
+
module UIControlWrapper
|
3
|
+
def when(events, options={}, &block)
|
4
|
+
@callback ||= {}
|
5
|
+
@callback[events] ||= []
|
6
|
+
|
7
|
+
unless options[:append]
|
8
|
+
@callback[events] = []
|
9
|
+
removeTarget(nil, action: nil, forControlEvents: events)
|
10
|
+
end
|
11
|
+
|
12
|
+
@callback[events] << block
|
13
|
+
addTarget(@callback[events].last, action:'call', forControlEvents: events)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module BubbleWrap
|
2
|
+
module UIViewControllerWrapper
|
3
|
+
# Short hand to get the content frame
|
4
|
+
#
|
5
|
+
# Return content frame: the application frame - navigation bar frame
|
6
|
+
def content_frame
|
7
|
+
app_frame = App.frame
|
8
|
+
navbar_height = self.navigationController.nil? ?
|
9
|
+
0 : self.navigationController.navigationBar.frame.size.height
|
10
|
+
CGRectMake(0, 0, app_frame.size.width, app_frame.size.height - navbar_height)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module BubbleWrap
|
2
|
+
module UIViewWrapper
|
3
|
+
def when_tapped(enableInteraction=true, &proc)
|
4
|
+
add_gesture_recognizer_helper(UITapGestureRecognizer.alloc.initWithTarget(self, action:'handle_gesture:'), enableInteraction, proc)
|
5
|
+
end
|
6
|
+
|
7
|
+
def when_pinched(enableInteraction=true, &proc)
|
8
|
+
add_gesture_recognizer_helper(UIPinchGestureRecognizer.alloc.initWithTarget(self, action:'handle_gesture:'), enableInteraction, proc)
|
9
|
+
end
|
10
|
+
|
11
|
+
def when_rotated(enableInteraction=true, &proc)
|
12
|
+
add_gesture_recognizer_helper(UIRotationGestureRecognizer.alloc.initWithTarget(self, action:'handle_gesture:'), enableInteraction, proc)
|
13
|
+
end
|
14
|
+
|
15
|
+
def when_swiped(enableInteraction=true, &proc)
|
16
|
+
add_gesture_recognizer_helper(UISwipeGestureRecognizer.alloc.initWithTarget(self, action:'handle_gesture:'), enableInteraction, proc)
|
17
|
+
end
|
18
|
+
|
19
|
+
def when_panned(enableInteraction=true, &proc)
|
20
|
+
add_gesture_recognizer_helper(UIPanGestureRecognizer.alloc.initWithTarget(self, action:'handle_gesture:'), enableInteraction, proc)
|
21
|
+
end
|
22
|
+
|
23
|
+
def when_pressed(enableInteraction=true, &proc)
|
24
|
+
add_gesture_recognizer_helper(UILongPressGestureRecognizer.alloc.initWithTarget(self, action:'handle_gesture:'), enableInteraction, proc)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.deprecated_methods
|
28
|
+
%w(whenTapped whenPinched whenRotated whenSwiped whenPanned whenPressed)
|
29
|
+
end
|
30
|
+
|
31
|
+
deprecated_methods.each do |method|
|
32
|
+
define_method(method) do |enableInteraction = true, &proc|
|
33
|
+
NSLog "[DEPRECATED - #{method}] please use #{method.underscore} instead."
|
34
|
+
send(method.underscore, enableInteraction, &proc)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def handle_gesture(recognizer)
|
41
|
+
@recognizers[recognizer].call(recognizer)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Adds the recognizer and keeps a strong reference to the Proc object.
|
45
|
+
def add_gesture_recognizer_helper(recognizer, enableInteraction, proc)
|
46
|
+
setUserInteractionEnabled true if enableInteraction && !isUserInteractionEnabled
|
47
|
+
self.addGestureRecognizer(recognizer)
|
48
|
+
|
49
|
+
@recognizers = {} unless @recognizers
|
50
|
+
@recognizers[recognizer] = proc
|
51
|
+
|
52
|
+
recognizer
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
"real_key" = "Real Key";
|
data/samples/gesture/Gemfile
CHANGED
data/samples/location/Gemfile
CHANGED
data/samples/osx/Gemfile
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
|
-
gem 'bubble-wrap', :path => "../../" # '~> 1.3.0'
|
3
|
+
gem 'bubble-wrap', :path => "../../" # '~> 1.3.0'
|
@@ -36,6 +36,12 @@ describe BubbleWrap::App do
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
describe '.info_plist' do
|
40
|
+
it 'returns the information property list hash' do
|
41
|
+
App.info_plist.should == NSBundle.mainBundle.infoDictionary
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
39
45
|
describe '.name' do
|
40
46
|
it 'returns the application name' do
|
41
47
|
App.name.should == 'testSuite'
|
@@ -3,9 +3,9 @@ def camera_picker
|
|
3
3
|
end
|
4
4
|
|
5
5
|
def example_info
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
{ UIImagePickerControllerMediaType => KUTTypeImage,
|
7
|
+
UIImagePickerControllerOriginalImage => UIImage.alloc.init,
|
8
|
+
UIImagePickerControllerMediaURL => NSURL.alloc.init}
|
9
9
|
end
|
10
10
|
|
11
11
|
describe BubbleWrap::Device::Camera do
|
@@ -103,6 +103,51 @@ describe "iOS" do
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
+
describe '.interface_orientation' do
|
107
|
+
|
108
|
+
describe 'portrait' do
|
109
|
+
it 'returns :portrait' do
|
110
|
+
BW::Device::Screen.interface_orientation(UIInterfaceOrientationPortrait).should == :portrait
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe 'portrait upside down' do
|
115
|
+
it 'returns :portrait_upside_down' do
|
116
|
+
BW::Device::Screen.interface_orientation(UIInterfaceOrientationPortraitUpsideDown).should == :portrait_upside_down
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'landscape left' do
|
121
|
+
it 'returns :landscape_left' do
|
122
|
+
BW::Device::Screen.interface_orientation(UIInterfaceOrientationLandscapeLeft).should == :landscape_left
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'landscape right' do
|
127
|
+
it 'returns :landscape_right' do
|
128
|
+
BW::Device::Screen.interface_orientation(UIInterfaceOrientationLandscapeRight).should == :landscape_right
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe 'unknown' do
|
133
|
+
it 'returns :unknown if fallback is false' do
|
134
|
+
BW::Device::Screen.interface_orientation(UIDeviceOrientationUnknown, false).should == :unknown
|
135
|
+
end
|
136
|
+
it 'returns Status bar orientation if fallback not specified' do
|
137
|
+
BW::Device::Screen.interface_orientation(UIDeviceOrientationUnknown).should == BW::Device::Screen.interface_orientation(UIApplication.sharedApplication.statusBarOrientation)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe 'any other input' do
|
142
|
+
it 'returns :unknown if fallback is false' do
|
143
|
+
BW::Device::Screen.interface_orientation('twiggy twiggy twiggy', false).should == :unknown
|
144
|
+
end
|
145
|
+
it 'returns Status bar orientation if fallback not specified' do
|
146
|
+
BW::Device::Screen.interface_orientation('twiggy twiggy twiggy').should == BW::Device::Screen.interface_orientation(UIApplication.sharedApplication.statusBarOrientation)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
106
151
|
describe '.width' do
|
107
152
|
it 'returns the current device screen width' do
|
108
153
|
BW::Device::Screen.width.should == 320.0 if BW::Device.iphone?
|
@@ -3,6 +3,9 @@ describe BubbleWrap::App do
|
|
3
3
|
describe '.alert' do
|
4
4
|
after do
|
5
5
|
@alert.dismissWithClickedButtonIndex(@alert.cancelButtonIndex, animated: false)
|
6
|
+
|
7
|
+
wait 0.3 do
|
8
|
+
end
|
6
9
|
end
|
7
10
|
|
8
11
|
describe "with only one string argument" do
|
@@ -138,10 +141,36 @@ describe BubbleWrap::App do
|
|
138
141
|
end
|
139
142
|
end
|
140
143
|
|
144
|
+
describe '.windows' do
|
145
|
+
it 'returns UIApplication.sharedApplication.windows' do
|
146
|
+
App.windows.should == UIApplication.sharedApplication.windows
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
141
150
|
describe '.window' do
|
142
151
|
it 'returns UIApplication.sharedApplication.keyWindow' do
|
143
152
|
App.window.should == UIApplication.sharedApplication.keyWindow
|
144
153
|
end
|
154
|
+
|
155
|
+
describe 'with UIActionSheet' do
|
156
|
+
|
157
|
+
it 'returns the correct window' do
|
158
|
+
action_sheet = UIActionSheet.alloc.init
|
159
|
+
action_sheet.cancelButtonIndex = (action_sheet.addButtonWithTitle("Cancel"))
|
160
|
+
|
161
|
+
old_window = App.window
|
162
|
+
window_count = App.windows.count
|
163
|
+
action_sheet.showInView(App.window)
|
164
|
+
wait 1 do
|
165
|
+
UIApplication.sharedApplication.windows.count.should > window_count
|
166
|
+
App.window.should == old_window
|
167
|
+
|
168
|
+
action_sheet.dismissWithClickedButtonIndex(action_sheet.cancelButtonIndex, animated: false)
|
169
|
+
|
170
|
+
App.window.should == old_window
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
145
174
|
end
|
146
175
|
|
147
176
|
describe '.run_after' do
|
@@ -65,4 +65,29 @@ describe BubbleWrap::Persistence do
|
|
65
65
|
BubbleWrap::Persistence[:arbitraryString].methods.should == 'test string'.methods
|
66
66
|
end
|
67
67
|
end
|
68
|
+
|
69
|
+
describe "deleting object" do
|
70
|
+
before do
|
71
|
+
BubbleWrap::Persistence['arbitraryString'] = 'foobarbaz'
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'can delete persisted object' do
|
75
|
+
BubbleWrap::Persistence.delete(:arbitraryString).should == 'foobarbaz'
|
76
|
+
BubbleWrap::Persistence['arbitraryString'].should.equal nil
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'returns nil when the object does not exist' do
|
80
|
+
BubbleWrap::Persistence.delete(:wrongKey).should == nil
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'must call synchronize' do
|
84
|
+
storage = NSUserDefaults.standardUserDefaults
|
85
|
+
def storage.synchronize; @sync_was_called = true; end
|
86
|
+
|
87
|
+
BubbleWrap::Persistence.delete(:arbitraryString)
|
88
|
+
|
89
|
+
storage.instance_variable_get(:@sync_was_called).should.equal true
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
68
93
|
end
|