ProMotion 2.4.2 → 2.5.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -13
  3. data/lib/ProMotion.rb +40 -14
  4. data/lib/ProMotion/cocoatouch/collection_view_cell.rb +8 -0
  5. data/lib/ProMotion/cocoatouch/collection_view_controller.rb +49 -0
  6. data/lib/ProMotion/cocoatouch/view_controller.rb +5 -0
  7. data/lib/ProMotion/collection/cell/collection_view_cell_module.rb +17 -0
  8. data/lib/ProMotion/collection/collection.rb +106 -0
  9. data/lib/ProMotion/collection/collection_builder.rb +39 -0
  10. data/lib/ProMotion/collection/collection_class_methods.rb +38 -0
  11. data/lib/ProMotion/collection/collection_screen.rb +8 -0
  12. data/lib/ProMotion/collection/data/collection_data.rb +32 -0
  13. data/lib/ProMotion/collection/data/collection_data_builder.rb +18 -0
  14. data/lib/ProMotion/delegate/delegate_module.rb +1 -1
  15. data/lib/ProMotion/ipad/split_screen.rb +0 -1
  16. data/lib/ProMotion/repl/live_reloader.rb +76 -0
  17. data/lib/ProMotion/repl/repl.rb +65 -0
  18. data/lib/ProMotion/screen/nav_bar_module.rb +8 -1
  19. data/lib/ProMotion/screen/screen_module.rb +26 -3
  20. data/lib/ProMotion/screen/screen_navigation.rb +3 -0
  21. data/lib/ProMotion/table/cell/table_view_cell_module.rb +11 -0
  22. data/lib/ProMotion/table/extensions/searchable.rb +11 -0
  23. data/lib/ProMotion/table/table.rb +12 -1
  24. data/lib/ProMotion/table/table_builder.rb +7 -5
  25. data/lib/ProMotion/tabs/tabs.rb +6 -3
  26. data/lib/ProMotion/version.rb +1 -1
  27. data/spec/functional/func_screen_spec.rb +25 -20
  28. data/spec/functional/func_split_screen_spec.rb +2 -2
  29. data/spec/unit/collection_screen_spec.rb +133 -0
  30. data/spec/unit/screen_helpers_spec.rb +1 -1
  31. data/spec/unit/screen_spec.rb +44 -6
  32. data/spec/unit/tables/table_module_spec.rb +28 -2
  33. data/spec/unit/tables/table_refreshable_spec.rb +25 -0
  34. data/spec/unit/tables/table_screen_spec.rb +0 -56
  35. data/spec/unit/tables/table_searchable_spec.rb +41 -0
  36. data/spec/unit/tables/table_view_cell_spec.rb +20 -1
  37. metadata +22 -5
@@ -0,0 +1,8 @@
1
+ module ProMotion
2
+ class CollectionScreen < CollectionViewController
3
+ include ProMotion::ScreenModule
4
+ include ProMotion::CollectionBuilder
5
+ include ProMotion::Collection
6
+ end
7
+ end
8
+
@@ -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
 
@@ -58,7 +58,6 @@ module ProMotion
58
58
  end
59
59
 
60
60
  def split_screen_setup(split, args)
61
- args[:icon] ||= args[:item] # TODO: Remove in PM 2.2+.
62
61
  if (args[:item] || args[:title]) && respond_to?(:create_tab_bar_item)
63
62
  split.tabBarItem = create_tab_bar_item(args)
64
63
  end
@@ -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
- self.navigationController.setNavigationBarHidden(args[:hide_nav_bar], animated: false) unless args[:hide_nav_bar].nil?
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) if args[:nav_bar]
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 ||= self.to_s
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] || tableView.sectionHeaderHeight
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
- return mp("Action not implemented: #{action.to_s}", force_color: :green) unless self.respond_to?(action)
5
- case self.method(action).arity
6
- when 0 then self.send(action) # Just call the method
7
- when 2 then self.send(action, arguments, index_path) # Send arguments and index path
8
- else self.send(action, arguments) # Send arguments
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
 
@@ -50,10 +50,13 @@ module ProMotion
50
50
  end
51
51
 
52
52
  def create_tab_bar_item(tab={})
53
- if tab[:system_icon] || tab[:icon]
54
- mp("`system_icon:` no longer supported. Use `system_item:` instead.", force_color: :yellow) if tab[:system_icon]
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