ProMotion 2.4.2 → 2.5.0.beta1
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 +4 -4
- data/README.md +15 -13
- data/lib/ProMotion.rb +40 -14
- data/lib/ProMotion/cocoatouch/collection_view_cell.rb +8 -0
- data/lib/ProMotion/cocoatouch/collection_view_controller.rb +49 -0
- data/lib/ProMotion/cocoatouch/view_controller.rb +5 -0
- data/lib/ProMotion/collection/cell/collection_view_cell_module.rb +17 -0
- data/lib/ProMotion/collection/collection.rb +106 -0
- data/lib/ProMotion/collection/collection_builder.rb +39 -0
- data/lib/ProMotion/collection/collection_class_methods.rb +38 -0
- data/lib/ProMotion/collection/collection_screen.rb +8 -0
- data/lib/ProMotion/collection/data/collection_data.rb +32 -0
- data/lib/ProMotion/collection/data/collection_data_builder.rb +18 -0
- data/lib/ProMotion/delegate/delegate_module.rb +1 -1
- data/lib/ProMotion/ipad/split_screen.rb +0 -1
- data/lib/ProMotion/repl/live_reloader.rb +76 -0
- data/lib/ProMotion/repl/repl.rb +65 -0
- data/lib/ProMotion/screen/nav_bar_module.rb +8 -1
- data/lib/ProMotion/screen/screen_module.rb +26 -3
- data/lib/ProMotion/screen/screen_navigation.rb +3 -0
- data/lib/ProMotion/table/cell/table_view_cell_module.rb +11 -0
- data/lib/ProMotion/table/extensions/searchable.rb +11 -0
- data/lib/ProMotion/table/table.rb +12 -1
- data/lib/ProMotion/table/table_builder.rb +7 -5
- data/lib/ProMotion/tabs/tabs.rb +6 -3
- data/lib/ProMotion/version.rb +1 -1
- data/spec/functional/func_screen_spec.rb +25 -20
- data/spec/functional/func_split_screen_spec.rb +2 -2
- data/spec/unit/collection_screen_spec.rb +133 -0
- data/spec/unit/screen_helpers_spec.rb +1 -1
- data/spec/unit/screen_spec.rb +44 -6
- data/spec/unit/tables/table_module_spec.rb +28 -2
- data/spec/unit/tables/table_refreshable_spec.rb +25 -0
- data/spec/unit/tables/table_screen_spec.rb +0 -56
- data/spec/unit/tables/table_searchable_spec.rb +41 -0
- data/spec/unit/tables/table_view_cell_spec.rb +20 -1
- metadata +22 -5
@@ -0,0 +1,32 @@
|
|
1
|
+
module ProMotion
|
2
|
+
class CollectionData
|
3
|
+
include ProMotion::Table::Utils
|
4
|
+
include ProMotion::CollectionDataBuilder
|
5
|
+
|
6
|
+
attr_accessor :data, :collection_view
|
7
|
+
|
8
|
+
def initialize(data, collection_view)
|
9
|
+
self.data = data
|
10
|
+
self.collection_view = WeakRef.new(collection_view)
|
11
|
+
end
|
12
|
+
|
13
|
+
def section(index)
|
14
|
+
sections.at(index) || []
|
15
|
+
end
|
16
|
+
|
17
|
+
def sections
|
18
|
+
self.data
|
19
|
+
end
|
20
|
+
|
21
|
+
def section_length(index)
|
22
|
+
section(index).length
|
23
|
+
end
|
24
|
+
|
25
|
+
def cell(params={})
|
26
|
+
params = index_path_to_section_index(params)
|
27
|
+
section = self.data[params[:section]]
|
28
|
+
c = section.at(params[:index].to_i)
|
29
|
+
set_data_cell_defaults(c)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ProMotion
|
2
|
+
module CollectionDataBuilder
|
3
|
+
def set_data_cell_defaults(data_cell)
|
4
|
+
data_cell[:cell_identifier] ||= PM::CollectionViewCell::KIdentifier
|
5
|
+
data_cell[:properties] ||= data_cell[:style] || data_cell[:styles]
|
6
|
+
|
7
|
+
data_cell[:accessory] = {
|
8
|
+
view: data_cell[:accessory],
|
9
|
+
value: data_cell[:accessory_value],
|
10
|
+
action: data_cell[:accessory_action],
|
11
|
+
arguments: data_cell[:accessory_arguments]
|
12
|
+
} unless data_cell[:accessory].nil? || data_cell[:accessory].is_a?(Hash)
|
13
|
+
|
14
|
+
data_cell
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -2,7 +2,7 @@ module ProMotion
|
|
2
2
|
module DelegateModule
|
3
3
|
include ProMotion::Support
|
4
4
|
include ProMotion::Tabs
|
5
|
-
include ProMotion::SplitScreen if UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad
|
5
|
+
include ProMotion::SplitScreen if UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad || (UIDevice.currentDevice.systemVersion.to_i >= 8 )
|
6
6
|
|
7
7
|
attr_accessor :window, :home_screen
|
8
8
|
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class LiveReloader
|
2
|
+
attr_reader :path_query, :opts
|
3
|
+
|
4
|
+
def initialize(path, opts={})
|
5
|
+
@path_query = path
|
6
|
+
@opts = opts
|
7
|
+
end
|
8
|
+
|
9
|
+
def watch(&callback)
|
10
|
+
log path_query
|
11
|
+
log live_file_paths
|
12
|
+
|
13
|
+
@live_reload_timer = every(opts[:interval] || 0.5) do
|
14
|
+
live_files.each do |live_file, modified_date|
|
15
|
+
if File.exist?(live_file) && File.mtime(live_file) > modified_date
|
16
|
+
new_code = File.read(live_file)
|
17
|
+
eval(new_code)
|
18
|
+
callback.call *[live_file, new_code, parse_class_names(new_code)].first(callback.arity)
|
19
|
+
reload_live_files
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
log "Watching #{path_query}."
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def stop
|
29
|
+
@live_reload_timer.invalidate if @live_reload_timer
|
30
|
+
@live_reload_timer = nil
|
31
|
+
log "Stopped."
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug?
|
36
|
+
@opts[:debug]
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def every(interval, &callback)
|
42
|
+
NSTimer.scheduledTimerWithTimeInterval(interval, target: callback, selector: 'call:', userInfo: nil, repeats: true)
|
43
|
+
end
|
44
|
+
|
45
|
+
def live_files
|
46
|
+
@live_files ||= live_file_paths.inject({}) do |out, live_file_path_file|
|
47
|
+
out[live_file_path_file] = File.mtime(live_file_path_file)
|
48
|
+
out
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def reload_live_files
|
53
|
+
@live_files = nil
|
54
|
+
live_files
|
55
|
+
end
|
56
|
+
|
57
|
+
def log(s)
|
58
|
+
puts s.inspect if debug?
|
59
|
+
s
|
60
|
+
end
|
61
|
+
|
62
|
+
def project_root_path
|
63
|
+
NSBundle.mainBundle.infoDictionary["ProjectRootPath"]
|
64
|
+
end
|
65
|
+
|
66
|
+
def live_file_paths
|
67
|
+
log Dir.glob("#{project_root_path}/#{path_query}").to_a
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_class_names(code)
|
71
|
+
log code.split("\n").map do |code_line|
|
72
|
+
matched = code_line.match(/^\s*class\s+(\S+)\s+</)
|
73
|
+
matched[1] if matched
|
74
|
+
end.to_a.compact.to_a
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
if RUBYMOTION_ENV == "development"
|
2
|
+
puts "Type `pm_live` to enable ProMotion's live reload system."
|
3
|
+
module Kernel
|
4
|
+
def pm_live(opts={})
|
5
|
+
@screen_watcher.stop if @screen_watcher
|
6
|
+
@view_watcher.stop if @view_watcher
|
7
|
+
if opts == false || opts.to_s.downcase == "off"
|
8
|
+
"Live reloading of PM screens is now off."
|
9
|
+
else
|
10
|
+
@screen_watcher = LiveReloader.new("app/screens/**/*.rb", opts).watch do |reloaded_file, new_code, class_names|
|
11
|
+
vcs = pm_all_view_controllers(UIApplication.sharedApplication.delegate.window.rootViewController)
|
12
|
+
vcs.each do |vc|
|
13
|
+
if pm_is_in_ancestry?(vc, class_names)
|
14
|
+
puts "Sending :on_live_reload to #{vc.inspect}." if opts[:debug]
|
15
|
+
vc.send(:on_live_reload) if vc.respond_to?(:on_live_reload)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
@view_watcher = LiveReloader.new("app/views/**/*.rb", opts).watch do |reloaded_file, new_code, class_names|
|
21
|
+
views = pm_all_views(UIApplication.sharedApplication.delegate.window)
|
22
|
+
views.each do |view|
|
23
|
+
if pm_is_in_ancestry?(view, class_names)
|
24
|
+
puts "Sending :on_live_reload to #{view.inspect}." if opts[:debug]
|
25
|
+
view.send(:on_live_reload) if view.respond_to?(:on_live_reload)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
"Live reloading of screens and views is now on."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias_method :pm_live_screens, :pm_live
|
34
|
+
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Very permissive. Might get unnecessary reloads. That's okay.
|
39
|
+
def pm_is_in_ancestry?(vc, screen_names)
|
40
|
+
screen_names.any? do |screen_name|
|
41
|
+
vc.class.to_s.include?(screen_name) ||
|
42
|
+
vc.class.ancestors.any? do |ancestor|
|
43
|
+
screen_name.include?(ancestor.to_s)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def pm_all_view_controllers(root_view_controller)
|
49
|
+
vcs = [ root_view_controller ]
|
50
|
+
if root_view_controller.respond_to?(:viewControllers)
|
51
|
+
vcs = vcs + root_view_controller.viewControllers.map{|vc| pm_all_view_controllers(vc) }
|
52
|
+
end
|
53
|
+
if root_view_controller.respond_to?(:childViewControllers)
|
54
|
+
vcs = vcs + root_view_controller.childViewControllers.map{|vc| pm_all_view_controllers(vc) }
|
55
|
+
end
|
56
|
+
vcs.flatten.uniq
|
57
|
+
end
|
58
|
+
|
59
|
+
def pm_all_views(root_view)
|
60
|
+
views = [ root_view ]
|
61
|
+
views = views + views.map{|v| v.subviews.map{|sv| pm_all_views(sv) } }
|
62
|
+
views.flatten.uniq
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -53,6 +53,8 @@ module ProMotion
|
|
53
53
|
alias_method :set_toolbar_button, :set_toolbar_items
|
54
54
|
|
55
55
|
def add_nav_bar(args = {})
|
56
|
+
args = self.class.get_nav_bar.merge(args)
|
57
|
+
return unless args[:nav_bar]
|
56
58
|
self.navigationController ||= begin
|
57
59
|
self.first_screen = true if self.respond_to?(:first_screen=)
|
58
60
|
nav = (args[:nav_controller] || NavigationController).alloc.initWithRootViewController(self)
|
@@ -61,7 +63,12 @@ module ProMotion
|
|
61
63
|
nav
|
62
64
|
end
|
63
65
|
self.navigationController.toolbarHidden = !args[:toolbar] unless args[:toolbar].nil?
|
64
|
-
|
66
|
+
end
|
67
|
+
|
68
|
+
def view_will_appear(animated)
|
69
|
+
if @screen_options && !@screen_options[:hide_nav_bar].nil?
|
70
|
+
self.navigationController.setNavigationBarHidden(@screen_options[:hide_nav_bar], animated: false)
|
71
|
+
end
|
65
72
|
end
|
66
73
|
|
67
74
|
private
|
@@ -5,15 +5,16 @@ module ProMotion
|
|
5
5
|
include ProMotion::Styling
|
6
6
|
include ProMotion::NavBarModule
|
7
7
|
include ProMotion::Tabs
|
8
|
-
include ProMotion::SplitScreen if UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad
|
8
|
+
include ProMotion::SplitScreen if UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad || (UIDevice.currentDevice.systemVersion.to_i >= 8 )
|
9
9
|
|
10
10
|
attr_accessor :parent_screen, :first_screen, :modal, :split_screen
|
11
11
|
|
12
12
|
def screen_init(args = {})
|
13
|
+
@screen_options = args
|
13
14
|
check_ancestry
|
14
15
|
resolve_title
|
15
16
|
apply_properties(args)
|
16
|
-
add_nav_bar(args)
|
17
|
+
add_nav_bar(args)
|
17
18
|
add_nav_bar_buttons
|
18
19
|
tab_bar_setup
|
19
20
|
try :on_init
|
@@ -38,6 +39,7 @@ module ProMotion
|
|
38
39
|
end
|
39
40
|
|
40
41
|
def view_will_appear(animated)
|
42
|
+
super
|
41
43
|
resolve_status_bar
|
42
44
|
self.will_appear
|
43
45
|
|
@@ -70,6 +72,18 @@ module ProMotion
|
|
70
72
|
def on_disappear; end
|
71
73
|
def on_dismiss; end
|
72
74
|
|
75
|
+
def did_receive_memory_warning
|
76
|
+
self.on_memory_warning
|
77
|
+
end
|
78
|
+
def on_memory_warning
|
79
|
+
mp "Received memory warning in #{self.inspect}. You should implement on_memory_warning in your secreen.", force_color: :red
|
80
|
+
end
|
81
|
+
|
82
|
+
def on_live_reload
|
83
|
+
self.view.subviews.each(&:removeFromSuperview)
|
84
|
+
on_load
|
85
|
+
end
|
86
|
+
|
73
87
|
def should_rotate(orientation)
|
74
88
|
case orientation
|
75
89
|
when UIInterfaceOrientationPortrait
|
@@ -175,6 +189,7 @@ module ProMotion
|
|
175
189
|
status_bar_hidden false
|
176
190
|
status_bar_style UIStatusBarStyleDefault
|
177
191
|
else
|
192
|
+
return status_bar_hidden true if UIApplication.sharedApplication.isStatusBarHidden
|
178
193
|
status_bar_hidden false
|
179
194
|
global_style = NSBundle.mainBundle.objectForInfoDictionaryKey("UIStatusBarStyle")
|
180
195
|
status_bar_style global_style ? Object.const_get(global_style) : UIStatusBarStyleDefault
|
@@ -218,7 +233,7 @@ module ProMotion
|
|
218
233
|
end
|
219
234
|
@title = t if t
|
220
235
|
@title_type = :text if t
|
221
|
-
@title
|
236
|
+
@title
|
222
237
|
end
|
223
238
|
|
224
239
|
def title_type
|
@@ -251,6 +266,14 @@ module ProMotion
|
|
251
266
|
@status_bar_animation || UIStatusBarAnimationSlide
|
252
267
|
end
|
253
268
|
|
269
|
+
def nav_bar(enabled, args={})
|
270
|
+
@nav_bar_args = ({ nav_bar: enabled }).merge(args)
|
271
|
+
end
|
272
|
+
|
273
|
+
def get_nav_bar
|
274
|
+
@nav_bar_args ||= { nav_bar: false }
|
275
|
+
end
|
276
|
+
|
254
277
|
def nav_bar_button(side, args={})
|
255
278
|
@nav_bar_button_args = args
|
256
279
|
@nav_bar_button_args[:side] = side
|
@@ -82,6 +82,9 @@ module ProMotion
|
|
82
82
|
# Instantiate screen if given a class
|
83
83
|
screen = screen.new if screen.respond_to?(:new)
|
84
84
|
|
85
|
+
# Store screen options
|
86
|
+
screen.instance_variable_set(:@screen_options, args)
|
87
|
+
|
85
88
|
# Set parent
|
86
89
|
screen.parent_screen = self if screen.respond_to?(:parent_screen=)
|
87
90
|
|
@@ -19,6 +19,17 @@ module ProMotion
|
|
19
19
|
set_accessory_type
|
20
20
|
end
|
21
21
|
|
22
|
+
def layoutSubviews
|
23
|
+
super
|
24
|
+
|
25
|
+
# Support changing sizes of the image view
|
26
|
+
if (data_cell[:image] && data_cell[:image].is_a?(Hash) && data_cell[:image][:size])
|
27
|
+
self.imageView.bounds = CGRectMake(0, 0, data_cell[:image][:size], data_cell[:image][:size]);
|
28
|
+
elsif (data_cell[:remote_image] && data_cell[:remote_image][:size])
|
29
|
+
self.imageView.bounds = CGRectMake(0, 0, data_cell[:remote_image][:size], data_cell[:remote_image][:size]);
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
22
33
|
protected
|
23
34
|
|
24
35
|
# TODO: Remove this in ProMotion 2.1. Just for migration purposes.
|
@@ -11,6 +11,8 @@ module ProMotion
|
|
11
11
|
search_bar.placeholder = params[:search_bar][:placeholder]
|
12
12
|
end
|
13
13
|
|
14
|
+
@no_results_text = params[:search_bar][:no_results] if params[:search_bar][:no_results]
|
15
|
+
|
14
16
|
@table_search_display_controller = UISearchDisplayController.alloc.initWithSearchBar(search_bar, contentsController: params[:content_controller])
|
15
17
|
@table_search_display_controller.delegate = params[:delegate]
|
16
18
|
@table_search_display_controller.searchResultsDataSource = params[:data_source]
|
@@ -39,10 +41,19 @@ module ProMotion
|
|
39
41
|
search_bar
|
40
42
|
end
|
41
43
|
|
44
|
+
def set_no_results_text(controller)
|
45
|
+
Dispatch::Queue.main.async do
|
46
|
+
controller.searchResultsTableView.subviews.each do |v|
|
47
|
+
v.text = @no_results_text if v.is_a?(UILabel)
|
48
|
+
end
|
49
|
+
end if @no_results_text
|
50
|
+
end
|
51
|
+
|
42
52
|
######### iOS methods, headless camel case #######
|
43
53
|
|
44
54
|
def searchDisplayController(controller, shouldReloadTableForSearchString:search_string)
|
45
55
|
self.promotion_table_data.search(search_string)
|
56
|
+
set_no_results_text(controller) if @no_results_text
|
46
57
|
true
|
47
58
|
end
|
48
59
|
|
@@ -23,6 +23,10 @@ module ProMotion
|
|
23
23
|
set_up_row_height
|
24
24
|
end
|
25
25
|
|
26
|
+
def on_live_reload
|
27
|
+
update_table_data
|
28
|
+
end
|
29
|
+
|
26
30
|
def check_table_data
|
27
31
|
mp("Missing #table_data method in TableScreen #{self.class.to_s}.", force_color: :red) unless self.respond_to?(:table_data)
|
28
32
|
end
|
@@ -287,6 +291,7 @@ module ProMotion
|
|
287
291
|
section = promotion_table_data.section(index)
|
288
292
|
view = section[:title_view]
|
289
293
|
view = section[:title_view].new if section[:title_view].respond_to?(:new)
|
294
|
+
view.on_load if view.respond_to?(:on_load)
|
290
295
|
view.title = section[:title] if view.respond_to?(:title=)
|
291
296
|
view
|
292
297
|
end
|
@@ -294,7 +299,13 @@ module ProMotion
|
|
294
299
|
def tableView(_, heightForHeaderInSection: index)
|
295
300
|
section = promotion_table_data.section(index)
|
296
301
|
if section[:title_view] || section[:title].to_s.length > 0
|
297
|
-
section[:title_view_height]
|
302
|
+
if section[:title_view_height]
|
303
|
+
section[:title_view_height]
|
304
|
+
elsif (section_header = tableView(_, viewForHeaderInSection: index)) && section_header.respond_to?(:height)
|
305
|
+
section_header.height
|
306
|
+
else
|
307
|
+
tableView.sectionHeaderHeight
|
308
|
+
end
|
298
309
|
else
|
299
310
|
0.0
|
300
311
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module ProMotion
|
2
2
|
module TableBuilder
|
3
3
|
def trigger_action(action, arguments, index_path)
|
4
|
-
|
5
|
-
case
|
6
|
-
when 0 then
|
7
|
-
when 2 then
|
8
|
-
else
|
4
|
+
action = (action.is_a?(Proc) ? action : method(action))
|
5
|
+
case arity = action.arity
|
6
|
+
when 0 then action.call # Just call the proc or the method
|
7
|
+
when 2 then action.call(arguments, index_path) # Send arguments and index path
|
8
|
+
else
|
9
|
+
mp("Action should not have optional parameters: #{action.to_s} in #{self.inspect}", force_color: :yellow) if arity < 0
|
10
|
+
action.call(arguments) # Send arguments
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
data/lib/ProMotion/tabs/tabs.rb
CHANGED
@@ -50,10 +50,13 @@ module ProMotion
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def create_tab_bar_item(tab={})
|
53
|
-
if tab[:system_icon]
|
54
|
-
mp("`system_icon:` no longer supported. Use `system_item:` instead.", force_color: :yellow)
|
55
|
-
mp("`icon:` no longer supported. Use `item:` instead.", force_color: :yellow) if tab[:icon]
|
53
|
+
if tab[:system_icon]
|
54
|
+
mp("`system_icon:` no longer supported. Use `system_item:` instead.", force_color: :yellow)
|
56
55
|
tab[:system_item] ||= tab[:system_icon]
|
56
|
+
end
|
57
|
+
|
58
|
+
if tab[:icon]
|
59
|
+
mp("`icon:` no longer supported. Use `item:` instead.", force_color: :yellow)
|
57
60
|
tab[:item] ||= tab[:icon]
|
58
61
|
end
|
59
62
|
|