ProMotion 0.5.2 → 0.6.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 +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +5 -1
- data/README.md +237 -138
- data/Rakefile +4 -9
- data/app/app_delegate.rb +3 -0
- data/app/screens/basic_screen.rb +27 -0
- data/lib/ProMotion.rb +3 -5
- data/lib/ProMotion/cocoatouch/SplitViewController.rb +25 -0
- data/lib/ProMotion/cocoatouch/TableViewController.rb +5 -5
- data/lib/ProMotion/cocoatouch/ViewController.rb +5 -5
- data/lib/ProMotion/{app_delegate.rb → delegate.rb} +23 -23
- data/lib/ProMotion/helpers/console.rb +6 -4
- data/lib/ProMotion/helpers/logger.rb +73 -0
- data/lib/ProMotion/helpers/view_helper.rb +45 -13
- data/lib/ProMotion/screen_helpers/_tables/_refreshable_table.rb +42 -0
- data/lib/ProMotion/screen_helpers/_tables/_searchable_table.rb +2 -2
- data/lib/ProMotion/screen_helpers/_tables/_sectioned_table.rb +46 -41
- data/lib/ProMotion/screen_helpers/_tables/grouped_table.rb +2 -1
- data/lib/ProMotion/screen_helpers/_tables/plain_table.rb +1 -0
- data/lib/ProMotion/screen_helpers/screen_elements.rb +16 -11
- data/lib/ProMotion/screen_helpers/screen_navigation.rb +15 -16
- data/lib/ProMotion/screen_helpers/screen_tabs.rb +20 -16
- data/lib/ProMotion/screen_helpers/split_screen.rb +42 -0
- data/lib/ProMotion/screens/_screen_module.rb +44 -35
- data/lib/ProMotion/screens/_table_screen_module.rb +18 -1
- data/lib/ProMotion/screens/screen.rb +1 -1
- data/lib/ProMotion/version.rb +1 -1
- data/spec/helpers/table_screen.rb +48 -0
- data/spec/helpers/table_screen_refreshable.rb +11 -0
- data/spec/helpers/table_screen_searchable.rb +5 -0
- data/spec/helpers/test_delegate.rb +9 -0
- data/spec/ios_version_spec.rb +6 -6
- data/spec/logger_spec.rb +68 -0
- data/spec/main_spec.rb +1 -1
- data/spec/screen_helpers_spec.rb +35 -6
- data/spec/{view_controller_spec.rb → screen_spec.rb} +1 -1
- data/spec/split_screen_in_tab_bar_spec.rb +49 -0
- data/spec/split_screen_open_screen_spec.rb +46 -0
- data/spec/split_screen_spec.rb +35 -0
- data/spec/table_screen_spec.rb +72 -0
- data/spec/view_helper_spec.rb +112 -8
- metadata +29 -8
- data/lib/ProMotion/helpers/tab_bar.rb +0 -115
- data/spec/helpers/.gitkeep +0 -0
@@ -22,13 +22,13 @@ module ProMotion::MotionTable
|
|
22
22
|
@contacts_search_display_controller.delegate = params[:delegate]
|
23
23
|
@contacts_search_display_controller.searchResultsDataSource = params[:data_source]
|
24
24
|
@contacts_search_display_controller.searchResultsDelegate = params[:search_results_delegate]
|
25
|
-
|
25
|
+
|
26
26
|
self.table_view.tableHeaderView = search_bar
|
27
27
|
end
|
28
28
|
alias :makeSearchable :make_searchable
|
29
29
|
|
30
30
|
######### iOS methods, headless camel case #######
|
31
|
-
|
31
|
+
|
32
32
|
def searchDisplayController(controller, shouldReloadTableForSearchString:search_string)
|
33
33
|
@mt_filtered_data = nil
|
34
34
|
@mt_filtered_data = []
|
@@ -1,12 +1,22 @@
|
|
1
1
|
module ProMotion::MotionTable
|
2
2
|
module SectionedTable
|
3
|
+
include ProMotion::ViewHelper
|
4
|
+
|
3
5
|
def table_setup
|
4
|
-
|
6
|
+
PM.logger.error "Missing #table_data method in TableScreen #{self.class.to_s}." unless self.respond_to?(:table_data)
|
5
7
|
|
6
8
|
self.view = self.create_table_view_from_data(self.table_data)
|
9
|
+
|
7
10
|
if self.class.respond_to?(:get_searchable) && self.class.get_searchable
|
8
11
|
self.make_searchable(content_controller: self, search_bar: self.class.get_searchable_params)
|
9
12
|
end
|
13
|
+
if self.class.respond_to?(:get_refreshable) && self.class.get_refreshable
|
14
|
+
if defined?(UIRefreshControl)
|
15
|
+
self.make_refreshable(self.class.get_refreshable_params)
|
16
|
+
else
|
17
|
+
PM.logger.warn "To use the refresh control on < iOS 6, you need to include the CocoaPod 'CKRefreshControl'."
|
18
|
+
end
|
19
|
+
end
|
10
20
|
end
|
11
21
|
|
12
22
|
# @param [Array] Array of table data
|
@@ -37,7 +47,9 @@ module ProMotion::MotionTable
|
|
37
47
|
end
|
38
48
|
|
39
49
|
def cell_at_section_and_index(section, index)
|
40
|
-
|
50
|
+
if section_at_index(section) && section_at_index(section)[:cells]
|
51
|
+
return section_at_index(section)[:cells].at(index)
|
52
|
+
end
|
41
53
|
end
|
42
54
|
alias :cellAtSectionAndIndex :cell_at_section_and_index
|
43
55
|
|
@@ -49,36 +61,22 @@ module ProMotion::MotionTable
|
|
49
61
|
elsif expected_arguments == 1 || expected_arguments == -1
|
50
62
|
self.send(action, arguments)
|
51
63
|
else
|
52
|
-
|
64
|
+
PM.logger.warn "#{action} expects #{expected_arguments} arguments. Maximum number of required arguments for an action is 1."
|
53
65
|
end
|
54
66
|
else
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def set_cell_attributes(element, args = {})
|
60
|
-
args.each do |k, v|
|
61
|
-
if v.is_a? Hash
|
62
|
-
v.each do
|
63
|
-
sub_element = element.send("#{k}")
|
64
|
-
set_cell_attributes(sub_element, v)
|
65
|
-
end
|
66
|
-
else
|
67
|
-
element.send("#{k}=", v) if element.respond_to?("#{k}=")
|
68
|
-
end
|
67
|
+
PM.logger.info "Action not implemented: #{action.to_s}"
|
69
68
|
end
|
70
|
-
element
|
71
69
|
end
|
72
70
|
|
73
71
|
def accessory_toggled_switch(switch)
|
74
72
|
table_cell = switch.superview
|
75
|
-
|
73
|
+
index_path = table_cell.superview.indexPathForCell(table_cell)
|
76
74
|
|
77
|
-
data_cell = cell_at_section_and_index(
|
75
|
+
data_cell = cell_at_section_and_index(index_path.section, index_path.row)
|
78
76
|
data_cell[:arguments] = {} unless data_cell[:arguments]
|
79
77
|
data_cell[:arguments][:value] = switch.isOn if data_cell[:arguments].is_a? Hash
|
80
78
|
data_cell[:accessory_action] ||= data_cell[:accessoryAction] # For legacy support
|
81
|
-
|
79
|
+
|
82
80
|
trigger_action(data_cell[:accessory_action], data_cell[:arguments]) if data_cell[:accessory_action]
|
83
81
|
end
|
84
82
|
|
@@ -105,14 +103,14 @@ module ProMotion::MotionTable
|
|
105
103
|
# Set table_data_index if you want the right hand index column (jumplist)
|
106
104
|
def sectionIndexTitlesForTableView(table_view)
|
107
105
|
if self.respond_to?(:table_data_index)
|
108
|
-
self.table_data_index
|
106
|
+
self.table_data_index
|
109
107
|
end
|
110
108
|
end
|
111
|
-
|
109
|
+
|
112
110
|
def remap_data_cell(data_cell)
|
113
111
|
# Re-maps legacy data cell calls
|
114
|
-
mappings = {
|
115
|
-
cell_style: :cellStyle,
|
112
|
+
mappings = {
|
113
|
+
cell_style: :cellStyle,
|
116
114
|
cell_identifier: :cellIdentifier,
|
117
115
|
cell_class: :cellClass,
|
118
116
|
masks_to_bounds: :masksToBounds,
|
@@ -138,14 +136,12 @@ module ProMotion::MotionTable
|
|
138
136
|
data_cell
|
139
137
|
end
|
140
138
|
|
141
|
-
def tableView(table_view, cellForRowAtIndexPath:
|
142
|
-
|
143
|
-
|
144
|
-
data_cell = cell_at_section_and_index(indexPath.section, indexPath.row)
|
139
|
+
def tableView(table_view, cellForRowAtIndexPath:index_path)
|
140
|
+
data_cell = cell_at_section_and_index(index_path.section, index_path.row)
|
145
141
|
return UITableViewCell.alloc.init unless data_cell
|
146
|
-
|
142
|
+
|
147
143
|
data_cell = self.remap_data_cell(data_cell)
|
148
|
-
|
144
|
+
|
149
145
|
data_cell[:cell_style] ||= UITableViewCellStyleDefault
|
150
146
|
data_cell[:cell_identifier] ||= "Cell"
|
151
147
|
cell_identifier = data_cell[:cell_identifier]
|
@@ -154,7 +150,7 @@ module ProMotion::MotionTable
|
|
154
150
|
table_cell = table_view.dequeueReusableCellWithIdentifier(cell_identifier)
|
155
151
|
unless table_cell
|
156
152
|
table_cell = data_cell[:cell_class].alloc.initWithStyle(data_cell[:cell_style], reuseIdentifier:cell_identifier)
|
157
|
-
|
153
|
+
|
158
154
|
# Add optimizations here
|
159
155
|
table_cell.layer.masksToBounds = true if data_cell[:masks_to_bounds]
|
160
156
|
table_cell.backgroundColor = data_cell[:background_color] if data_cell[:background_color]
|
@@ -163,9 +159,9 @@ module ProMotion::MotionTable
|
|
163
159
|
end
|
164
160
|
|
165
161
|
if data_cell[:cell_class_attributes]
|
166
|
-
|
162
|
+
set_attributes table_cell, data_cell[:cell_class_attributes]
|
167
163
|
end
|
168
|
-
|
164
|
+
|
169
165
|
if data_cell[:accessory_view]
|
170
166
|
table_cell.accessoryView = data_cell[:accessory_view]
|
171
167
|
table_cell.accessoryView.autoresizingMask = UIViewAutoresizingFlexibleWidth
|
@@ -177,7 +173,7 @@ module ProMotion::MotionTable
|
|
177
173
|
|
178
174
|
if data_cell[:accessory] && data_cell[:accessory] == :switch
|
179
175
|
switch_view = UISwitch.alloc.initWithFrame(CGRectZero)
|
180
|
-
switch_view.addTarget(self, action: "accessory_toggled_switch:", forControlEvents:UIControlEventValueChanged)
|
176
|
+
switch_view.addTarget(self, action: "accessory_toggled_switch:", forControlEvents:UIControlEventValueChanged)
|
181
177
|
switch_view.on = true if data_cell[:accessory_checked]
|
182
178
|
table_cell.accessoryView = switch_view
|
183
179
|
end
|
@@ -199,9 +195,9 @@ module ProMotion::MotionTable
|
|
199
195
|
table_cell.image_size = data_cell[:remote_image][:size] if data_cell[:remote_image][:size] && table_cell.respond_to?("image_size=")
|
200
196
|
table_cell.imageView.setImageWithURL(url, placeholderImage: placeholder)
|
201
197
|
table_cell.imageView.layer.masksToBounds = true
|
202
|
-
table_cell.imageView.layer.cornerRadius = data_cell[:remote_image][:radius]
|
198
|
+
table_cell.imageView.layer.cornerRadius = data_cell[:remote_image][:radius] if data_cell[:remote_image].has_key?(:radius)
|
203
199
|
else
|
204
|
-
|
200
|
+
PM.logger.error "ProMotion Warning: to use remote_image with TableScreen you need to include the CocoaPod 'SDWebImage'."
|
205
201
|
end
|
206
202
|
elsif data_cell[:image]
|
207
203
|
table_cell.imageView.layer.masksToBounds = true
|
@@ -240,7 +236,7 @@ module ProMotion::MotionTable
|
|
240
236
|
|
241
237
|
unless ui_label == true
|
242
238
|
label ||= UILabel.alloc.initWithFrame(CGRectZero)
|
243
|
-
|
239
|
+
set_attributes label, data_cell[:styles][:label]
|
244
240
|
table_cell.contentView.addSubview label
|
245
241
|
end
|
246
242
|
# hackery
|
@@ -254,9 +250,18 @@ module ProMotion::MotionTable
|
|
254
250
|
return table_cell
|
255
251
|
end
|
256
252
|
|
257
|
-
def tableView(
|
258
|
-
cell = cell_at_section_and_index(
|
259
|
-
|
253
|
+
def tableView(tableView, heightForRowAtIndexPath:index_path)
|
254
|
+
cell = cell_at_section_and_index(index_path.section, index_path.row)
|
255
|
+
if cell[:height]
|
256
|
+
cell[:height].to_f
|
257
|
+
else
|
258
|
+
tableView.rowHeight
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def tableView(table_view, didSelectRowAtIndexPath:index_path)
|
263
|
+
cell = cell_at_section_and_index(index_path.section, index_path.row)
|
264
|
+
table_view.deselectRowAtIndexPath(index_path, animated: true)
|
260
265
|
cell[:arguments] ||= {}
|
261
266
|
cell[:arguments][:cell] = cell if cell[:arguments].is_a?(Hash)
|
262
267
|
trigger_action(cell[:action], cell[:arguments]) if cell[:action]
|
@@ -1,28 +1,33 @@
|
|
1
1
|
module ProMotion
|
2
2
|
module ScreenElements
|
3
3
|
include ProMotion::ViewHelper
|
4
|
-
|
5
|
-
def add(
|
6
|
-
|
7
|
-
set_attributes(v, attrs)
|
8
|
-
end
|
9
|
-
self.view.addSubview(v)
|
10
|
-
v
|
4
|
+
|
5
|
+
def add(element, attrs = {})
|
6
|
+
add_to self.view, element, attrs
|
11
7
|
end
|
12
8
|
alias :add_element :add
|
13
9
|
alias :add_view :add
|
14
10
|
|
15
|
-
def remove(
|
16
|
-
|
17
|
-
|
11
|
+
def remove(element)
|
12
|
+
element.removeFromSuperview
|
13
|
+
element = nil
|
18
14
|
end
|
19
15
|
alias :remove_element :remove
|
20
16
|
alias :remove_view :remove
|
17
|
+
|
18
|
+
def add_to(parent_element, element, attrs = {})
|
19
|
+
if attrs && attrs.length > 0
|
20
|
+
set_attributes(element, attrs)
|
21
|
+
set_easy_attributes(parent_element, element, attrs)
|
22
|
+
end
|
23
|
+
parent_element.addSubview element
|
24
|
+
element
|
25
|
+
end
|
21
26
|
|
22
27
|
def bounds
|
23
28
|
return self.view.bounds
|
24
29
|
end
|
25
|
-
|
30
|
+
|
26
31
|
def frame
|
27
32
|
return self.view.frame
|
28
33
|
end
|
@@ -2,14 +2,17 @@ module ProMotion
|
|
2
2
|
module ScreenNavigation
|
3
3
|
|
4
4
|
def open_screen(screen, args = {})
|
5
|
-
|
5
|
+
|
6
6
|
# Apply properties to instance
|
7
7
|
screen = setup_screen_for_open(screen, args)
|
8
8
|
ensure_wrapper_controller_in_place(screen, args)
|
9
9
|
|
10
|
-
screen.send(:on_load) if screen.respond_to?(:on_load)
|
10
|
+
screen.send(:on_load) if screen.respond_to?(:on_load)
|
11
11
|
animated = args[:animated] || true
|
12
12
|
|
13
|
+
return self.split_screen.detail_screen = screen if args[:in_detail] && self.split_screen
|
14
|
+
return self.split_screen.master_screen = screen if args[:in_master] && self.split_screen
|
15
|
+
|
13
16
|
if args[:close_all]
|
14
17
|
open_root_screen screen
|
15
18
|
|
@@ -36,18 +39,15 @@ module ProMotion
|
|
36
39
|
def open_root_screen(screen)
|
37
40
|
app_delegate.open_root_screen(screen)
|
38
41
|
end
|
39
|
-
alias :fresh_start :open_root_screen
|
40
42
|
|
41
43
|
def app_delegate
|
42
44
|
UIApplication.sharedApplication.delegate
|
43
45
|
end
|
44
|
-
|
45
|
-
# TODO: De-uglify this method.
|
46
|
+
|
46
47
|
def close_screen(args = {})
|
47
48
|
args ||= {}
|
48
49
|
args[:animated] ||= true
|
49
|
-
|
50
|
-
# Pop current view, maybe with arguments, if in navigation controller
|
50
|
+
|
51
51
|
if self.is_modal?
|
52
52
|
close_modal_screen args
|
53
53
|
|
@@ -56,8 +56,8 @@ module ProMotion
|
|
56
56
|
send_on_return(args) # TODO: this would be better implemented in a callback or view_did_disappear.
|
57
57
|
|
58
58
|
else
|
59
|
-
|
60
|
-
|
59
|
+
PM.logger.warn "Tried to close #{self.to_s}; however, this screen isn't modal or in a nav bar."
|
60
|
+
|
61
61
|
end
|
62
62
|
end
|
63
63
|
alias :close :close_screen
|
@@ -69,7 +69,6 @@ module ProMotion
|
|
69
69
|
else
|
70
70
|
self.parent_screen.send(:on_return)
|
71
71
|
end
|
72
|
-
ProMotion::Screen.current_screen = self.parent_screen
|
73
72
|
end
|
74
73
|
end
|
75
74
|
|
@@ -78,14 +77,14 @@ module ProMotion
|
|
78
77
|
end
|
79
78
|
|
80
79
|
def push_view_controller(vc, nav_controller=nil)
|
81
|
-
|
80
|
+
unless self.navigation_controller
|
81
|
+
PM.logger.error "You need a nav_bar if you are going to push #{vc.to_s} onto it."
|
82
|
+
end
|
82
83
|
nav_controller ||= self.navigation_controller
|
84
|
+
vc.first_screen = false if vc.respond_to?(:first_screen=)
|
83
85
|
nav_controller.pushViewController(vc, animated: true)
|
84
86
|
end
|
85
87
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
88
|
protected
|
90
89
|
|
91
90
|
def setup_screen_for_open(screen, args={})
|
@@ -97,7 +96,7 @@ module ProMotion
|
|
97
96
|
screen.parent_screen = self if screen.respond_to?("parent_screen=")
|
98
97
|
screen.title = args[:title] if args[:title] && screen.respond_to?("title=")
|
99
98
|
screen.modal = args[:modal] if args[:modal] && screen.respond_to?("modal=")
|
100
|
-
|
99
|
+
|
101
100
|
# Hide bottom bar?
|
102
101
|
screen.hidesBottomBarWhenPushed = args[:hide_tab_bar] == true
|
103
102
|
|
@@ -134,7 +133,7 @@ module ProMotion
|
|
134
133
|
end
|
135
134
|
|
136
135
|
else
|
137
|
-
|
136
|
+
PM.logger.error "No tab bar item '#{tab_name}'"
|
138
137
|
end
|
139
138
|
end
|
140
139
|
|
@@ -9,31 +9,35 @@ module ProMotion
|
|
9
9
|
screens.map! { |s| s.respond_to?(:new) ? s.new : s } # Initialize any classes
|
10
10
|
|
11
11
|
screens.each do |s|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
12
|
+
s = s.new if s.respond_to?(:new)
|
13
|
+
|
14
|
+
s.tabBarItem.tag = tag_index
|
15
|
+
|
16
|
+
s.parent_screen = self if self.is_a?(UIViewController) && s.respond_to?("parent_screen=")
|
17
|
+
s.tab_bar = tab_bar_controller if s.respond_to?("tab_bar=")
|
18
|
+
|
19
|
+
vc = s.respond_to?(:main_controller) ? s.main_controller : s
|
20
|
+
view_controllers << vc
|
21
|
+
|
22
|
+
tag_index += 1
|
23
|
+
|
23
24
|
s.on_load if s.respond_to?(:on_load)
|
24
25
|
end
|
25
26
|
|
26
27
|
tab_bar_controller.viewControllers = view_controllers
|
27
28
|
tab_bar_controller
|
28
29
|
end
|
29
|
-
|
30
|
+
|
30
31
|
# Open a UITabBarController with the specified screens as the
|
31
32
|
# root view controller of the current app.
|
32
33
|
# @param [Array] A comma-delimited list of screen classes or instances.
|
33
34
|
# @return [UITabBarController]
|
34
35
|
def open_tab_bar(*screens)
|
35
36
|
tab_bar = tab_bar_controller(*screens)
|
36
|
-
|
37
|
+
|
38
|
+
a = self.respond_to?(:load_root_screen) ? self : UIApplication.sharedApplication.delegate
|
39
|
+
|
40
|
+
a.load_root_screen(tab_bar)
|
37
41
|
tab_bar
|
38
42
|
end
|
39
43
|
|
@@ -62,12 +66,12 @@ module ProMotion
|
|
62
66
|
title = tab[:title] if tab[:title]
|
63
67
|
tab[:tag] ||= @current_tag ||= 0
|
64
68
|
@current_tag = tab[:tag] + 1
|
65
|
-
|
69
|
+
|
66
70
|
tab_bar_item = create_tab_bar_icon(tab[:system_icon], tab[:tag]) if tab[:system_icon]
|
67
71
|
tab_bar_item = create_tab_bar_icon_custom(title, tab[:icon], tab[:tag]) if tab[:icon]
|
68
|
-
|
72
|
+
|
69
73
|
tab_bar_item.badgeValue = tab[:badge_number].to_s unless tab[:badge_number].nil? || tab[:badge_number] <= 0
|
70
|
-
|
74
|
+
|
71
75
|
return tab_bar_item
|
72
76
|
end
|
73
77
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ProMotion
|
2
|
+
module SplitScreen
|
3
|
+
def split_screen_controller(master, detail)
|
4
|
+
master_main = master.navigationController ? master.navigationController : master
|
5
|
+
detail_main = detail.navigationController ? detail.navigationController : detail
|
6
|
+
|
7
|
+
split = SplitViewController.alloc.init
|
8
|
+
split.viewControllers = [ master_main, detail_main ]
|
9
|
+
split.delegate = self
|
10
|
+
|
11
|
+
[ master, detail ].map { |s| s.split_screen = split if s.respond_to?(:split_screen=) }
|
12
|
+
|
13
|
+
split
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_split_screen(master, detail, args={})
|
17
|
+
master = master.new if master.respond_to?(:new)
|
18
|
+
detail = detail.new if detail.respond_to?(:new)
|
19
|
+
|
20
|
+
[ master, detail ].map { |s| s.on_load if s.respond_to?(:on_load) }
|
21
|
+
|
22
|
+
split_screen_controller master, detail
|
23
|
+
end
|
24
|
+
|
25
|
+
def open_split_screen(master, detail, args={})
|
26
|
+
split = create_split_screen(master, detail, args)
|
27
|
+
open split, args
|
28
|
+
split
|
29
|
+
end
|
30
|
+
|
31
|
+
# UISplitViewControllerDelegate methods
|
32
|
+
|
33
|
+
def splitViewController(svc, willHideViewController: vc, withBarButtonItem: button, forPopoverController: pc)
|
34
|
+
button.title = vc.title
|
35
|
+
svc.detail_screen.navigationItem.leftBarButtonItem = button;
|
36
|
+
end
|
37
|
+
|
38
|
+
def splitViewController(svc, willShowViewController: vc, invalidatingBarButtonItem: barButtonItem)
|
39
|
+
svc.detail_screen.navigationItem.leftBarButtonItem = nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|