formotion 1.6 → 1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +0 -3
- data/Gemfile +4 -1
- data/LIST_OF_ROW_TYPES.md +58 -6
- data/Rakefile +6 -2
- data/app/app_delegate.rb +6 -0
- data/examples/KitchenSink/Gemfile +2 -1
- data/examples/KitchenSink/Rakefile +9 -4
- data/examples/KitchenSink/app/app_delegate.rb +20 -5
- data/examples/KitchenSink/resources/arrow-up.png +0 -0
- data/examples/KitchenSink/resources/arrow-up@2x.png +0 -0
- data/examples/KitchenSink/resources/email.png +0 -0
- data/examples/KitchenSink/resources/email@2x.png +0 -0
- data/examples/Persistence/app/app_delegate.rb +2 -0
- data/examples/Persistence/app/controller.rb +66 -0
- data/lib/formotion/controller/form_controller.rb +1 -1
- data/lib/formotion/form/form.rb +14 -5
- data/lib/formotion/model/formable.rb +8 -6
- data/lib/formotion/patch/ui_text_field.rb +15 -1
- data/lib/formotion/row/row.rb +19 -3
- data/lib/formotion/row/row_cell_builder.rb +48 -2
- data/lib/formotion/row_type/button.rb +2 -2
- data/lib/formotion/row_type/date_row.rb +8 -1
- data/lib/formotion/row_type/image_row.rb +2 -2
- data/lib/formotion/row_type/map_row.rb +60 -23
- data/lib/formotion/row_type/object_row.rb +2 -2
- data/lib/formotion/row_type/picker_row.rb +32 -0
- data/lib/formotion/row_type/string_row.rb +5 -2
- data/lib/formotion/row_type/web_link_row.rb +48 -0
- data/lib/formotion/version.rb +1 -1
- data/resources/camera.png +0 -0
- data/resources/camera@2x.png +0 -0
- data/spec/functional/map_row_spec.rb +151 -4
- data/spec/functional/web_link_row_spec.rb +35 -0
- data/spec/row_spec.rb +109 -1
- data/spec/row_type/web_link_spec.rb +23 -0
- metadata +13 -2
@@ -39,7 +39,7 @@ class UITextField
|
|
39
39
|
# block takes argument textField
|
40
40
|
def on_end(&block)
|
41
41
|
add_delegate_method do
|
42
|
-
@delegate.
|
42
|
+
@delegate.textFieldDidEndEditing_callback = block
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -114,6 +114,11 @@ class UITextField_Delegate
|
|
114
114
|
if self.textFieldShouldEndEditing_callback
|
115
115
|
return self.textFieldShouldEndEditing_callback.call(theTextField)
|
116
116
|
end
|
117
|
+
|
118
|
+
if Device.ios_version >= "7.0"
|
119
|
+
theTextField.text = theTextField.text.gsub("\u00a0", " ").strip
|
120
|
+
end
|
121
|
+
|
117
122
|
true
|
118
123
|
end
|
119
124
|
|
@@ -127,6 +132,15 @@ class UITextField_Delegate
|
|
127
132
|
if self.shouldChangeCharactersInRange_callback
|
128
133
|
return self.shouldChangeCharactersInRange_callback.call(theTextField, range, string)
|
129
134
|
end
|
135
|
+
|
136
|
+
# fix for UITextField in iOS7 http://stackoverflow.com/questions/19569688/uitextfield-spacebar-does-not-advance-cursor-in-ios-7/20129483#20129483
|
137
|
+
if Device.ios_version >= "7.0"
|
138
|
+
if range.location == theTextField.text.length && string == " "
|
139
|
+
theTextField.text = theTextField.text.stringByAppendingString("\u00a0")
|
140
|
+
return false
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
130
144
|
true
|
131
145
|
end
|
132
146
|
|
data/lib/formotion/row/row.rb
CHANGED
@@ -9,6 +9,10 @@ module Formotion
|
|
9
9
|
:value,
|
10
10
|
# set as cell.titleLabel.text
|
11
11
|
:title,
|
12
|
+
# set as cell.imageView.image
|
13
|
+
:image,
|
14
|
+
# an image placeholder for cell.imageView.image when using remote images
|
15
|
+
:image_placeholder,
|
12
16
|
# set as cell.detailLabel.text
|
13
17
|
:subtitle,
|
14
18
|
# configures the type of input this is (string, phone, switch, etc)
|
@@ -101,7 +105,13 @@ module Formotion
|
|
101
105
|
# Cell selection style
|
102
106
|
# OPTIONS: :blue, :gray, :none
|
103
107
|
# DEFAULT is :blue
|
104
|
-
:selection_style
|
108
|
+
:selection_style,
|
109
|
+
|
110
|
+
# The following apply only to weblink rows
|
111
|
+
|
112
|
+
# Whether or not to display a warning to the user before leaving the app.
|
113
|
+
# DEFAULT is false
|
114
|
+
:warn
|
105
115
|
]
|
106
116
|
PROPERTIES.each {|prop|
|
107
117
|
attr_accessor prop
|
@@ -139,6 +149,8 @@ module Formotion
|
|
139
149
|
attr_accessor :on_tap_callback
|
140
150
|
# callback for when a row is tapped
|
141
151
|
attr_accessor :on_delete_callback
|
152
|
+
# callback for when a row is exited
|
153
|
+
attr_accessor :on_end_callback
|
142
154
|
|
143
155
|
# RowType object
|
144
156
|
attr_accessor :object
|
@@ -264,7 +276,7 @@ module Formotion
|
|
264
276
|
end
|
265
277
|
|
266
278
|
def text_alignment=(alignment)
|
267
|
-
@text_alignment = const_int_get("
|
279
|
+
@text_alignment = const_int_get("NSTextAlignment", alignment)
|
268
280
|
end
|
269
281
|
|
270
282
|
def selection_style=(style)
|
@@ -305,6 +317,10 @@ module Formotion
|
|
305
317
|
self.on_begin_callback = block
|
306
318
|
end
|
307
319
|
|
320
|
+
def on_end(&block)
|
321
|
+
self.on_end_callback = block
|
322
|
+
end
|
323
|
+
|
308
324
|
# Used in :button type rows
|
309
325
|
def on_tap(&block)
|
310
326
|
self.on_tap_callback = block
|
@@ -398,7 +414,7 @@ module Formotion
|
|
398
414
|
UITextFieldViewModeNever, UITextFieldViewModeAlways, UITextFieldViewModeWhileEditing,
|
399
415
|
UITextFieldViewModeUnlessEditing, NSDateFormatterShortStyle, NSDateFormatterMediumStyle,
|
400
416
|
NSDateFormatterLongStyle, NSDateFormatterFullStyle,
|
401
|
-
|
417
|
+
NSTextAlignmentRight, NSTextAlignmentCenter, NSTextAlignmentLeft
|
402
418
|
]
|
403
419
|
end
|
404
420
|
end
|
@@ -23,13 +23,20 @@ module Formotion
|
|
23
23
|
cell.textLabel.text = new_value
|
24
24
|
end
|
25
25
|
|
26
|
+
if row.image
|
27
|
+
Formotion::RowCellBuilder.set_image(cell, row)
|
28
|
+
observe(row, "image") do |old_value, new_value|
|
29
|
+
Formotion::RowCellBuilder.set_image(cell, row)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
26
33
|
cell.detailTextLabel.text = row.subtitle
|
27
34
|
observe(row, "subtitle") do |old_value, new_value|
|
28
35
|
cell.detailTextLabel.text = new_value
|
29
36
|
end
|
30
37
|
|
31
38
|
edit_field = row.object.build_cell(cell)
|
32
|
-
|
39
|
+
|
33
40
|
if edit_field and edit_field.respond_to?("accessibilityLabel=")
|
34
41
|
label = row.accessibility
|
35
42
|
label = row.title unless label
|
@@ -39,5 +46,44 @@ module Formotion
|
|
39
46
|
[cell, edit_field]
|
40
47
|
end
|
41
48
|
|
49
|
+
def self.set_image(cell, row)
|
50
|
+
if row.image.is_a?(NSURL) || (row.image.is_a?(String) && row.image.include?("http"))
|
51
|
+
# Use a remote image helper to set the image.
|
52
|
+
image_url = row.image
|
53
|
+
image_url = NSURL.URLWithString(image_url) unless image_url.is_a?(NSURL)
|
54
|
+
|
55
|
+
placeholder = row.image_placeholder
|
56
|
+
placeholder = UIImage.imageNamed(placeholder) if placeholder.is_a?(String)
|
57
|
+
|
58
|
+
if cell.imageView.respond_to?("setImageWithURLRequest:placeholderImage:success:failure:")
|
59
|
+
# Use AFNetworking
|
60
|
+
request = NSURLRequest.requestWithURL(image_url)
|
61
|
+
cell.imageView.setImageWithURLRequest(request, placeholderImage: placeholder, success: ->(request, response, image) {
|
62
|
+
cell.imageView.image = image
|
63
|
+
cell.setNeedsLayout
|
64
|
+
}, failure: ->(request, response, error) {
|
65
|
+
|
66
|
+
})
|
67
|
+
elsif cell.imageView.respond_to?("setImageWithURL:placeholderImage:completed:")
|
68
|
+
cell.imageView.setImageWithURL(image_url, placeholderImage: placeholder, completed: ->(image, error, cacheType) {
|
69
|
+
cell.imageView.image = image
|
70
|
+
cell.setNeedsLayout
|
71
|
+
})
|
72
|
+
elsif cell.imageView.respond_to?("setImageWithURL:placeholder:")
|
73
|
+
# Use JMImageCache
|
74
|
+
JMImageCache.sharedCache.imageForURL(image_url, completionBlock: ->(downloadedImage) {
|
75
|
+
cell.imageView.image = downloadedImage
|
76
|
+
cell.setNeedsLayout
|
77
|
+
})
|
78
|
+
else
|
79
|
+
raise "Please add the AFNetworking, SDWebImage, or JMImageCache pods to your project to use remote images in Formotion"
|
80
|
+
end
|
81
|
+
|
82
|
+
else
|
83
|
+
# Just set the image like normal
|
84
|
+
cell.imageView.image = (row.image.is_a? String) ? UIImage.imageNamed(row.image) : row.image
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
42
88
|
end
|
43
|
-
end
|
89
|
+
end
|
@@ -20,8 +20,8 @@ module Formotion
|
|
20
20
|
((self.frame.size.send(dimen) - frame.size.send(dimen)) / 2.0)
|
21
21
|
}
|
22
22
|
|
23
|
-
self.textLabel.center = CGPointMake(self.frame.size.width / 2
|
24
|
-
self.detailTextLabel.center = CGPointMake(self.frame.size.width / 2
|
23
|
+
self.textLabel.center = CGPointMake(self.frame.size.width / 2, self.textLabel.center.y)
|
24
|
+
self.detailTextLabel.center = CGPointMake(self.frame.size.width / 2, self.detailTextLabel.center.y)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
nil
|
@@ -40,6 +40,12 @@ module Formotion
|
|
40
40
|
|
41
41
|
def after_build(cell)
|
42
42
|
self.row.text_field.inputView = self.picker
|
43
|
+
# work around an iOS7 bug: http://bit.ly/KcwKSv
|
44
|
+
if row.picker_mode == :countdown
|
45
|
+
self.picker.setDate(self.picker.date, animated:true)
|
46
|
+
picker.countDownDuration = self.row.value
|
47
|
+
end
|
48
|
+
|
43
49
|
update
|
44
50
|
end
|
45
51
|
|
@@ -49,13 +55,14 @@ module Formotion
|
|
49
55
|
picker.datePickerMode = self.picker_mode
|
50
56
|
picker.hidden = false
|
51
57
|
picker.date = self.date_value || Time.now
|
58
|
+
picker.countDownDuration = self.row.value if row.picker_mode == :countdown
|
52
59
|
picker.minuteInterval = self.row.minute_interval if self.row.minute_interval
|
53
60
|
|
54
61
|
picker.when(UIControlEventValueChanged) do
|
55
62
|
if self.row.picker_mode == :countdown
|
56
63
|
self.row.value = @picker.countDownDuration
|
57
64
|
else
|
58
|
-
self.row.value = Time.at(@picker.date).to_i
|
65
|
+
self.row.value = Time.at(@picker.date).to_i
|
59
66
|
end
|
60
67
|
update
|
61
68
|
end
|
@@ -15,7 +15,7 @@ module Formotion
|
|
15
15
|
def build_cell(cell)
|
16
16
|
cell.selectionStyle = self.row.selection_style || UITableViewCellSelectionStyleBlue
|
17
17
|
# only show the "plus" when editable
|
18
|
-
add_plus_accessory(cell) if row.editable?
|
18
|
+
add_plus_accessory(cell) if row.editable? && (row.value == nil)
|
19
19
|
|
20
20
|
observe(self.row, "value") do |old_value, new_value|
|
21
21
|
@image_view.image = new_value
|
@@ -25,7 +25,7 @@ module Formotion
|
|
25
25
|
else
|
26
26
|
self.row.row_height = 44
|
27
27
|
# only show the "plus" when editable
|
28
|
-
add_plus_accessory(cell) if row.editable?
|
28
|
+
add_plus_accessory(cell) if row.editable? && (row.value == nil)
|
29
29
|
end
|
30
30
|
row.form.reload_data
|
31
31
|
end
|
@@ -3,31 +3,35 @@ motion_require 'base'
|
|
3
3
|
module Formotion
|
4
4
|
module RowType
|
5
5
|
class MapRowData
|
6
|
-
|
6
|
+
|
7
7
|
attr_accessor :pin, :options
|
8
8
|
#attr_accessor :title, :subtitle, :coordinate
|
9
|
-
|
10
|
-
def initialize(
|
11
|
-
@title=title
|
12
|
-
@subtitle=subtitle
|
13
|
-
@coordinate=
|
14
|
-
@options=options
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
@title=options[:title]
|
12
|
+
@subtitle=options[:subtitle]
|
13
|
+
@coordinate=options[:coord]
|
14
|
+
@options=options[:options]
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def title
|
18
18
|
@title
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def subtitle
|
22
22
|
@subtitle
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def coordinate
|
26
|
-
@coordinate
|
26
|
+
if @coordinate.is_a? CLCircularRegion
|
27
|
+
@coordinate.center
|
28
|
+
else
|
29
|
+
@coordinate
|
30
|
+
end
|
27
31
|
end
|
28
|
-
|
32
|
+
|
29
33
|
end
|
30
|
-
|
34
|
+
|
31
35
|
class MapRow < Base
|
32
36
|
include BW::KVO
|
33
37
|
|
@@ -35,34 +39,67 @@ module Formotion
|
|
35
39
|
|
36
40
|
def set_pin
|
37
41
|
return unless row.value
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
|
43
|
+
unless row.value.is_a?(Hash)
|
44
|
+
coord = (row.value.is_a?(Array) and row.value.size==2) ? CLLocationCoordinate2D.new(row.value[0], row.value[1]) : row.value
|
45
|
+
row.value = {coord: coord, pin: {coord:coord}}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set Defaults
|
49
|
+
row.value = {
|
50
|
+
animated: true,
|
51
|
+
type: MKMapTypeStandard,
|
52
|
+
enabled: true
|
53
|
+
}.merge(row.value)
|
54
|
+
|
55
|
+
if row.value[:coord].is_a?(CLLocationCoordinate2D)
|
56
|
+
region = MKCoordinateRegionMakeWithDistance(row.value[:coord], 400.0, 480.0)
|
57
|
+
elsif row.value[:coord].is_a?(CLCircularRegion)
|
58
|
+
region = MKCoordinateRegionMakeWithDistance(
|
59
|
+
row.value[:coord].center,
|
60
|
+
row.value[:coord].radius * 2,
|
61
|
+
row.value[:coord].radius * 2
|
62
|
+
)
|
63
|
+
else
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
if row.value[:pin]
|
68
|
+
row.value[:pin] = {title: nil, subtitle:nil}.merge(row.value[:pin]) #Defaults
|
69
|
+
|
70
|
+
@map_row_data = MapRowData.new(row.value[:pin])
|
71
|
+
@map_view.removeAnnotations(@map_view.annotations)
|
44
72
|
@map_view.addAnnotation(@map_row_data)
|
73
|
+
@map_view.selectAnnotation(@map_row_data, animated:row.value[:animated]) if row.value[:pin][:title]
|
45
74
|
end
|
75
|
+
|
76
|
+
@map_view.setUserInteractionEnabled(row.value[:enabled])
|
77
|
+
@map_view.setMapType(row.value[:type])
|
78
|
+
@map_view.setRegion(region, animated:row.value[:animated])
|
46
79
|
end
|
47
|
-
|
80
|
+
|
48
81
|
def annotations
|
49
82
|
@map_view.annotations
|
50
83
|
end
|
51
84
|
|
85
|
+
def map
|
86
|
+
@map_view
|
87
|
+
end
|
88
|
+
|
52
89
|
def build_cell(cell)
|
53
90
|
cell.selectionStyle = self.row.selection_style || UITableViewCellSelectionStyleBlue
|
54
91
|
|
55
92
|
@map_view = MKMapView.alloc.init
|
56
93
|
@map_view.delegate = self
|
57
|
-
|
94
|
+
|
58
95
|
set_pin
|
59
|
-
|
96
|
+
|
60
97
|
observe(self.row, "value") do |old_value, new_value|
|
61
98
|
break_with_semaphore do
|
62
99
|
set_pin
|
63
100
|
end
|
64
101
|
end
|
65
|
-
|
102
|
+
|
66
103
|
@map_view.tag = MAP_VIEW_TAG
|
67
104
|
@map_view.contentMode = UIViewContentModeScaleAspectFit
|
68
105
|
@map_view.backgroundColor = UIColor.clearColor
|
@@ -17,7 +17,7 @@ module Formotion
|
|
17
17
|
|
18
18
|
field.clearButtonMode = UITextFieldViewModeWhileEditing
|
19
19
|
field.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter
|
20
|
-
field.textAlignment = row.text_alignment ||
|
20
|
+
field.textAlignment = row.text_alignment || NSTextAlignmentRight
|
21
21
|
|
22
22
|
field.keyboardType = keyboardType
|
23
23
|
|
@@ -54,7 +54,7 @@ module Formotion
|
|
54
54
|
field
|
55
55
|
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
# Used when row.value changes
|
59
59
|
def update_text_field(new_value)
|
60
60
|
self.row.text_field.text = row_value.to_s
|
@@ -8,6 +8,38 @@ module Formotion
|
|
8
8
|
include RowType::ItemsMapper
|
9
9
|
include RowType::MultiChoiceRow
|
10
10
|
|
11
|
+
def input_accessory_view(input_accessory)
|
12
|
+
case input_accessory
|
13
|
+
when :done
|
14
|
+
@input_accessory ||= begin
|
15
|
+
tool_bar = UIToolbar.alloc.initWithFrame([[0, 0], [0, 44]])
|
16
|
+
tool_bar.autoresizingMask = UIViewAutoresizingFlexibleWidth
|
17
|
+
tool_bar.translucent = true
|
18
|
+
|
19
|
+
left_space = UIBarButtonItem.alloc.initWithBarButtonSystemItem(
|
20
|
+
UIBarButtonSystemItemFlexibleSpace,
|
21
|
+
target: nil,
|
22
|
+
action: nil)
|
23
|
+
|
24
|
+
done_button = UIBarButtonItem.alloc.initWithBarButtonSystemItem(
|
25
|
+
UIBarButtonSystemItemDone,
|
26
|
+
target: self,
|
27
|
+
action: :done_editing)
|
28
|
+
|
29
|
+
tool_bar.items = [left_space, done_button]
|
30
|
+
|
31
|
+
tool_bar
|
32
|
+
end
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Callback for "Done" button in input_accessory_view
|
39
|
+
def done_editing
|
40
|
+
self.row.text_field.endEditing(true)
|
41
|
+
end
|
42
|
+
|
11
43
|
def after_build(cell)
|
12
44
|
self.row.text_field.inputView = self.picker
|
13
45
|
self.row.text_field.text = name_for_value(row.value).to_s
|
@@ -33,7 +33,7 @@ module Formotion
|
|
33
33
|
|
34
34
|
field.clearButtonMode = UITextFieldViewModeWhileEditing
|
35
35
|
field.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter
|
36
|
-
field.textAlignment = row.text_alignment ||
|
36
|
+
field.textAlignment = row.text_alignment || NSTextAlignmentRight
|
37
37
|
|
38
38
|
field.keyboardType = keyboardType
|
39
39
|
|
@@ -112,6 +112,10 @@ module Formotion
|
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
115
|
+
field.on_end do |text_field|
|
116
|
+
row.on_end_callback && row.on_end_callback.call
|
117
|
+
end
|
118
|
+
|
115
119
|
field.on_begin do |text_field|
|
116
120
|
row.on_begin_callback && row.on_begin_callback.call
|
117
121
|
end
|
@@ -152,7 +156,6 @@ module Formotion
|
|
152
156
|
@input_accessory ||= begin
|
153
157
|
tool_bar = UIToolbar.alloc.initWithFrame([[0, 0], [0, 44]])
|
154
158
|
tool_bar.autoresizingMask = UIViewAutoresizingFlexibleWidth
|
155
|
-
tool_bar.barStyle = UIBarStyleBlack
|
156
159
|
tool_bar.translucent = true
|
157
160
|
|
158
161
|
left_space = UIBarButtonItem.alloc.initWithBarButtonSystemItem(
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Formotion
|
2
|
+
module RowType
|
3
|
+
class WebLinkRow < ObjectRow
|
4
|
+
|
5
|
+
def after_build(cell)
|
6
|
+
super
|
7
|
+
|
8
|
+
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator
|
9
|
+
self.row.text_field.hidden = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_select(tableView, tableViewDelegate)
|
13
|
+
if is_url?
|
14
|
+
if row.warn.nil? || row.warn == false
|
15
|
+
App.open_url row.value
|
16
|
+
else
|
17
|
+
warn
|
18
|
+
end
|
19
|
+
else
|
20
|
+
raise StandardError, "Row value for WebLinkRow should be a URL string or instance of NSURL."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def is_url?
|
25
|
+
(row.value.is_a?(String) && row.value[0..3] == "http") || row.value.is_a?(NSURL)
|
26
|
+
end
|
27
|
+
|
28
|
+
def warn
|
29
|
+
row.warn = {} unless row.warn.is_a? Hash #Convert value from true to a hash
|
30
|
+
row.warn = {
|
31
|
+
title: "Leaving #{App.name}",
|
32
|
+
message: "This action will leave #{App.name} and open Safari.",
|
33
|
+
buttons: ["Cancel", "OK"]
|
34
|
+
}.merge(row.warn)
|
35
|
+
|
36
|
+
BW::UIAlertView.new({
|
37
|
+
title: row.warn[:title],
|
38
|
+
message: row.warn[:message],
|
39
|
+
buttons: row.warn[:buttons],
|
40
|
+
cancel_button_index: 0
|
41
|
+
}) do |alert|
|
42
|
+
App.open_url(row.value) unless alert.clicked_button.cancel?
|
43
|
+
end.show
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|