ProMotion 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +6 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +5 -1
  5. data/README.md +237 -138
  6. data/Rakefile +4 -9
  7. data/app/app_delegate.rb +3 -0
  8. data/app/screens/basic_screen.rb +27 -0
  9. data/lib/ProMotion.rb +3 -5
  10. data/lib/ProMotion/cocoatouch/SplitViewController.rb +25 -0
  11. data/lib/ProMotion/cocoatouch/TableViewController.rb +5 -5
  12. data/lib/ProMotion/cocoatouch/ViewController.rb +5 -5
  13. data/lib/ProMotion/{app_delegate.rb → delegate.rb} +23 -23
  14. data/lib/ProMotion/helpers/console.rb +6 -4
  15. data/lib/ProMotion/helpers/logger.rb +73 -0
  16. data/lib/ProMotion/helpers/view_helper.rb +45 -13
  17. data/lib/ProMotion/screen_helpers/_tables/_refreshable_table.rb +42 -0
  18. data/lib/ProMotion/screen_helpers/_tables/_searchable_table.rb +2 -2
  19. data/lib/ProMotion/screen_helpers/_tables/_sectioned_table.rb +46 -41
  20. data/lib/ProMotion/screen_helpers/_tables/grouped_table.rb +2 -1
  21. data/lib/ProMotion/screen_helpers/_tables/plain_table.rb +1 -0
  22. data/lib/ProMotion/screen_helpers/screen_elements.rb +16 -11
  23. data/lib/ProMotion/screen_helpers/screen_navigation.rb +15 -16
  24. data/lib/ProMotion/screen_helpers/screen_tabs.rb +20 -16
  25. data/lib/ProMotion/screen_helpers/split_screen.rb +42 -0
  26. data/lib/ProMotion/screens/_screen_module.rb +44 -35
  27. data/lib/ProMotion/screens/_table_screen_module.rb +18 -1
  28. data/lib/ProMotion/screens/screen.rb +1 -1
  29. data/lib/ProMotion/version.rb +1 -1
  30. data/spec/helpers/table_screen.rb +48 -0
  31. data/spec/helpers/table_screen_refreshable.rb +11 -0
  32. data/spec/helpers/table_screen_searchable.rb +5 -0
  33. data/spec/helpers/test_delegate.rb +9 -0
  34. data/spec/ios_version_spec.rb +6 -6
  35. data/spec/logger_spec.rb +68 -0
  36. data/spec/main_spec.rb +1 -1
  37. data/spec/screen_helpers_spec.rb +35 -6
  38. data/spec/{view_controller_spec.rb → screen_spec.rb} +1 -1
  39. data/spec/split_screen_in_tab_bar_spec.rb +49 -0
  40. data/spec/split_screen_open_screen_spec.rb +46 -0
  41. data/spec/split_screen_spec.rb +35 -0
  42. data/spec/table_screen_spec.rb +72 -0
  43. data/spec/view_helper_spec.rb +112 -8
  44. metadata +29 -8
  45. data/lib/ProMotion/helpers/tab_bar.rb +0 -115
  46. data/spec/helpers/.gitkeep +0 -0
data/Rakefile CHANGED
@@ -3,20 +3,15 @@ require 'motion/project'
3
3
  require 'bundler/gem_tasks'
4
4
  Bundler.setup
5
5
  Bundler.require
6
- # require 'motion-table'
7
6
 
8
7
  Motion::Project::App.setup do |app|
9
- # Use `rake config' to see complete project settings.
10
8
  app.name = 'ProMotionTest'
11
- app.version = "0.3.0"
12
-
9
+ app.version = "0.99.0" # I've got 99 problems and the test app's version isn't one of them
10
+ app.redgreen_style = :focused # :focused, :full
13
11
 
14
12
  # Devices
15
- app.deployment_target = "5.0"
16
- app.device_family = [:iphone, :ipad]
13
+ app.deployment_target = "6.0"
14
+ app.device_family = [:ipad] # so we can test split screen capability
17
15
 
18
16
  app.detect_dependencies = true
19
-
20
- # Preload screens
21
- # app.files = Dir.glob(File.join(app.project_dir, 'lib/**/*.rb')) | app.files
22
17
  end
data/app/app_delegate.rb CHANGED
@@ -1,2 +1,5 @@
1
1
  class AppDelegate
2
+ def on_load(app, options)
3
+ open BasicScreen
4
+ end
2
5
  end
@@ -0,0 +1,27 @@
1
+ class BasicScreen < PM::Screen
2
+ title "Basic"
3
+
4
+ def on_init
5
+ # Fires right after the screen is initialized
6
+ end
7
+
8
+ def on_load
9
+ # Fires just before a screen is opened for the first time.
10
+ end
11
+
12
+ def will_appear
13
+ # Fires every time the screen will appear
14
+ end
15
+
16
+ def on_appear
17
+ # Fires just after the screen appears somewhere (after animations are complete)
18
+ end
19
+
20
+ def will_disappear
21
+ # Fires just before the screen will disappear
22
+ end
23
+
24
+ def on_disappear
25
+ # Fires after the screen is fully hidden
26
+ end
27
+ end
data/lib/ProMotion.rb CHANGED
@@ -5,10 +5,8 @@ end
5
5
  require "ProMotion/version"
6
6
 
7
7
  Motion::Project::App.setup do |app|
8
- # app_delegate = Dir.glob(File.join(File.dirname(__FILE__), 'ProMotion/app_delegate.rb'))
9
- # app.files = Dir.glob(File.join(File.dirname(__FILE__), 'ProMotion/**/*.rb')) | app_delegate | app.files
10
-
11
8
  original_files = app.files
12
- app.files = FileList[File.join(File.dirname(__FILE__), 'ProMotion/**/*.rb')].exclude(File.join(File.dirname(__FILE__), 'ProMotion/app_delegate.rb'))
13
- app.files = app.files | Dir.glob(File.join(File.dirname(__FILE__), 'ProMotion/app_delegate.rb')) | original_files
9
+ delegate = File.join(File.dirname(__FILE__), 'ProMotion/delegate.rb')
10
+ promotion_files = FileList[File.join(File.dirname(__FILE__), 'ProMotion/**/*.rb')].exclude(delegate).to_a
11
+ app.files = (promotion_files << delegate) + original_files
14
12
  end
@@ -0,0 +1,25 @@
1
+ class SplitViewController < UISplitViewController
2
+ def master_screen
3
+ s = self.viewControllers.first
4
+ s.respond_to?(:visibleViewController) ? s.visibleViewController : s
5
+ end
6
+ def detail_screen
7
+ s = self.viewControllers.last
8
+ s.respond_to?(:visibleViewController) ? s.visibleViewController : s
9
+ end
10
+ def master_screen=(s)
11
+ self.viewControllers = [s.main_controller, self.viewControllers.last]
12
+ end
13
+ def detail_screen=(s)
14
+ # set the button from the old detail screen to the new one
15
+ button = detail_screen.navigationItem.leftBarButtonItem
16
+ s.navigationItem.leftBarButtonItem = button
17
+
18
+ vc = s.respond_to?(:main_controller) ? s.main_controller : s
19
+
20
+ self.viewControllers = [self.viewControllers.first, vc]
21
+ end
22
+ def screens=(s_array)
23
+ self.viewControllers = s_array
24
+ end
25
+ end
@@ -20,15 +20,15 @@ module ProMotion
20
20
  super
21
21
  self.view_did_appear(animated) if self.respond_to?("view_did_appear:")
22
22
  end
23
-
23
+
24
24
  def viewWillDisappear(animated)
25
25
  self.view_will_disappear(animated) if self.respond_to?("view_will_disappear:")
26
- super
26
+ super
27
27
  end
28
-
28
+
29
29
  def viewDidDisappear(animated)
30
30
  self.view_did_disappear(animated) if self.respond_to?("view_did_disappear:")
31
- super
31
+ super
32
32
  end
33
33
 
34
34
  def shouldAutorotateToInterfaceOrientation(orientation)
@@ -42,7 +42,7 @@ module ProMotion
42
42
  def willRotateToInterfaceOrientation(orientation, duration:duration)
43
43
  self.will_rotate(orientation, duration)
44
44
  end
45
-
45
+
46
46
  def didRotateFromInterfaceOrientation(orientation)
47
47
  self.on_rotate
48
48
  end
@@ -20,17 +20,17 @@ module ProMotion
20
20
  super
21
21
  self.view_did_appear(animated) if self.respond_to?("view_did_appear:")
22
22
  end
23
-
23
+
24
24
  def viewWillDisappear(animated)
25
25
  self.view_will_disappear(animated) if self.respond_to?("view_will_disappear:")
26
- super
26
+ super
27
27
  end
28
-
28
+
29
29
  def viewDidDisappear(animated)
30
30
  if self.respond_to?("view_did_disappear:")
31
31
  self.view_did_disappear(animated)
32
32
  end
33
- super
33
+ super
34
34
  end
35
35
 
36
36
  def shouldAutorotateToInterfaceOrientation(orientation)
@@ -44,7 +44,7 @@ module ProMotion
44
44
  def willRotateToInterfaceOrientation(orientation, duration:duration)
45
45
  self.will_rotate(orientation, duration)
46
46
  end
47
-
47
+
48
48
  def didRotateFromInterfaceOrientation(orientation)
49
49
  self.on_rotate
50
50
  end
@@ -1,20 +1,19 @@
1
1
  module ProMotion
2
- class AppDelegateParent
2
+ class Delegate
3
3
  include ProMotion::ScreenTabs
4
+ include ProMotion::SplitScreen if NSBundle.mainBundle.infoDictionary["UIDeviceFamily"].include?("2") # Only with iPad
4
5
  attr_accessor :window
5
-
6
- def application(application, didFinishLaunchingWithOptions:launch_options)
7
- return true if RUBYMOTION_ENV == "test"
8
6
 
9
- Console.log(" Your AppDelegate (usually in app_delegate.rb) needs an on_load(options) method.", with_color: Console::RED_COLOR) unless self.respond_to?("on_load:")
7
+ def application(application, didFinishLaunchingWithOptions:launch_options)
8
+ unless self.respond_to?(:on_load)
9
+ PM.logger.error "Your AppDelegate (usually in app_delegate.rb) needs an on_load(application, options) method."
10
+ end
10
11
 
11
12
  on_load(application, launch_options)
12
13
 
13
- open_home_screen if has_home_screen
14
-
15
14
  true
16
15
  end
17
-
16
+
18
17
  def app_delegate
19
18
  UIApplication.sharedApplication.delegate
20
19
  end
@@ -23,35 +22,35 @@ module ProMotion
23
22
  self.app_delegate.window
24
23
  end
25
24
 
25
+ def home(screen)
26
+ screen = screen.new if screen.respond_to?(:new)
27
+ @home_screen = screen
28
+ end
29
+
26
30
  def load_root_screen(new_screen)
27
31
  new_screen = new_screen.main_controller if new_screen.respond_to?(:main_controller)
28
32
 
29
- self.window ||= UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
33
+ self.window ||= self.ui_window.alloc.initWithFrame(UIScreen.mainScreen.bounds)
30
34
  self.window.rootViewController = new_screen
31
35
  self.window.makeKeyAndVisible
32
36
  end
33
-
34
- def open_screen(screen)
35
- home(screen)
36
- end
37
- alias :open :open_screen
38
-
39
- def home(screen)
40
- screen = screen.new if screen.respond_to?(:new)
41
- @home_screen = screen
37
+
38
+ def ui_window
39
+ (defined?(Motion) && defined?(Motion::Xray) && defined?(Motion::Xray::XrayWindow)) ? Motion::Xray::XrayWindow : UIWindow
42
40
  end
43
41
 
44
- def open_root_screen(new_screen)
45
- home(new_screen)
42
+ def open_screen(screen, args={})
43
+ home(screen)
46
44
  open_home_screen
47
45
  end
48
- alias :fresh_start :open_root_screen
46
+ alias :open :open_screen
47
+ alias :open_root_screen :open_screen
49
48
 
50
49
  def open_home_screen
51
50
  get_home_screen.send(:on_load) if get_home_screen.respond_to?(:on_load)
52
51
  load_root_screen get_home_screen
53
52
  end
54
-
53
+
55
54
  def get_home_screen
56
55
  @home_screen
57
56
  end
@@ -60,4 +59,5 @@ module ProMotion
60
59
  @home_screen.nil? == false
61
60
  end
62
61
  end
63
- end
62
+ class AppDelegateParent < Delegate; end # For backwards compatibility
63
+ end
@@ -3,23 +3,25 @@ module ProMotion
3
3
  NAME = "RubyMotion::Console: "
4
4
  DEFAULT_COLOR = [ '', '' ]
5
5
  RED_COLOR = [ "\e[0;31m", "\e[0m" ] # Must be in double quotes
6
- GREEN_COLOR = [ "\e[0;32m", "\e[0m" ]
7
- PURPLE_COLOR = [ "\e[0;35m", "\e[0m" ]
6
+ GREEN_COLOR = [ "\e[0;32m", "\e[0m" ]
7
+ PURPLE_COLOR = [ "\e[0;35m", "\e[0m" ]
8
8
 
9
9
  class << self
10
10
  def log(log, with_color:color)
11
11
  return if RUBYMOTION_ENV == "test"
12
+ PM.logger.deprecated "ProMotion::Console.log is deprecated. Use PM.logger (see README)"
12
13
  puts color[0] + NAME + log + color[1]
13
14
  end
14
-
15
+
15
16
  def log(log, withColor:color)
16
17
  return if RUBYMOTION_ENV == "test"
17
- warn "[DEPRECATION] `log(log, withColor:color)` is deprecated. Use `log(log, with_color:color)`"
18
+ PM.logger.deprecated "ProMotion::Console.log is deprecated. Use PM.logger (see README)"
18
19
  self.log(log, with_color:color)
19
20
  end
20
21
 
21
22
  def log(log)
22
23
  return if RUBYMOTION_ENV == "test"
24
+ PM.logger.deprecated "ProMotion::Console.log is deprecated. Use PM.logger (see README)"
23
25
  log(log, with_color: DEFAULT_COLOR)
24
26
  end
25
27
  end
@@ -0,0 +1,73 @@
1
+ module ProMotion
2
+ class Logger
3
+ attr_accessor :level
4
+
5
+ NAME = "ProMotion::Logger: "
6
+
7
+ COLORS = {
8
+ default: [ '', '' ],
9
+ red: [ "\e[0;31m", "\e[0m" ],
10
+ green: [ "\e[0;32m", "\e[0m" ],
11
+ yellow: [ "\e[0;33m", "\e[0m" ],
12
+ blue: [ "\e[0;34m", "\e[0m" ],
13
+ purple: [ "\e[0;35m", "\e[0m" ],
14
+ cyan: [ "\e[0;36m", "\e[0m" ]
15
+ }
16
+
17
+ LEVELS = {
18
+ off: [],
19
+ error: [:error],
20
+ warn: [:error, :warn],
21
+ info: [:error, :warn, :info],
22
+ verbose: [:error, :warn, :info, :debug, :verbose],
23
+ debug: [:error, :warn, :info, :debug, :verbose]
24
+ }
25
+
26
+ def level
27
+ @level ||= :debug
28
+ end
29
+
30
+ def levels
31
+ LEVELS[self.level] || []
32
+ end
33
+
34
+ # Usage: PM.logger.log("ERROR", "message here", :red)
35
+ def log(label, message_text, color)
36
+ return if RUBYMOTION_ENV == "test"
37
+ color = COLORS[color] || COLORS[:default]
38
+ puts color[0] + NAME + "[#{label}] #{message_text}" + color[1]
39
+ end
40
+
41
+ def error(message)
42
+ self.log('ERROR', message, :red) if self.levels.include?(:error)
43
+ end
44
+
45
+ def deprecated(message)
46
+ self.log('DEPRECATED', message, :yellow) if self.levels.include?(:warn)
47
+ end
48
+
49
+ def warn(message)
50
+ self.log('WARN', message, :yellow) if self.levels.include?(:warn)
51
+ end
52
+
53
+ def debug(message)
54
+ self.log('DEBUG', message, :purple) if self.levels.include?(:debug)
55
+ end
56
+
57
+ def info(message)
58
+ self.log('INFO', message, :green) if self.levels.include?(:info)
59
+ end
60
+
61
+ end
62
+
63
+ module_function
64
+
65
+ def logger
66
+ @logger ||= Logger.new
67
+ end
68
+
69
+ def logger=(log)
70
+ @logger = log
71
+ end
72
+
73
+ end
@@ -1,26 +1,57 @@
1
1
  module ProMotion
2
2
  module ViewHelper
3
3
  def set_attributes(element, args = {})
4
- args.each do |k, v|
5
- if v.is_a? Hash
6
- # TODO: Do this recursively
7
- v.each do |k2, v2|
8
- sub_element = element.send("#{k}")
9
- sub_element.send("#{k2}=", v2) if sub_element.respond_to?("#{k2}=")
10
- end
11
- elsif v.is_a? Array
12
- element.send("#{k}", *v) if element.respond_to?("#{k}")
13
- else
14
- element.send("#{k}=", v) if element.respond_to?("#{k}=")
4
+ args.each { |k, v| set_attribute(element, k, v) }
5
+ element
6
+ end
7
+
8
+ def set_attribute(element, k, v)
9
+ if v.is_a?(Hash) && element.respond_to?(k)
10
+ sub_element = element.send(k)
11
+ set_attributes sub_element, v
12
+ elsif v.is_a?(Array) && element.respond_to?("#{k}")
13
+ element.send("#{k}", *v)
14
+ elsif element.respond_to?("#{k}=")
15
+ element.send("#{k}=", v)
16
+ else
17
+ # Doesn't respond. Check if snake case.
18
+ if k.to_s.include?("_")
19
+ set_attribute(element, objective_c_method_name(k), v)
15
20
  end
16
21
  end
17
22
  element
18
23
  end
19
24
 
25
+ def objective_c_method_name(meth)
26
+ meth.split('_').inject([]){ |buffer,e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
27
+ end
28
+
29
+ def set_easy_attributes(parent, element, args={})
30
+ attributes = {}
31
+
32
+ if args[:resize]
33
+ attributes[:autoresizingMask] = UIViewAutoresizingNone
34
+ attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleLeftMargin if args[:resize].include?(:left)
35
+ attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleRightMargin if args[:resize].include?(:right)
36
+ attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleTopMargin if args[:resize].include?(:top)
37
+ attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleBottomMargin if args[:resize].include?(:bottom)
38
+ attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleWidth if args[:resize].include?(:width)
39
+ attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleHeight if args[:resize].include?(:height)
40
+ end
41
+
42
+ if [:left, :top, :width, :height].select{ |a| args[a] && args[a] != :auto }.length == 4
43
+ attributes[:frame] = CGRectMake(args[:left], args[:top], args[:width], args[:height])
44
+ end
45
+
46
+ set_attributes element, attributes
47
+ element
48
+ end
49
+
20
50
  def frame_from_array(array)
51
+ PM.logger.deprecated "`frame_from_array` is deprecated and will be removed. Use RubyMotion's built-in [[x, y], [width, height]]."
21
52
  return CGRectMake(array[0], array[1], array[2], array[3]) if array.length == 4
22
- Console.log(" - frame_from_array expects an array with four elements: [x, y, width, height]", with_color: Console::RED_COLOR)
23
- CGRectZero
53
+ PM.logger.error "frame_from_array expects an array with four elements: [x, y, width, height]"
54
+ CGRectZero.dup
24
55
  end
25
56
 
26
57
  def content_height(view)
@@ -35,5 +66,6 @@ module ProMotion
35
66
  end
36
67
  height
37
68
  end
69
+
38
70
  end
39
71
  end
@@ -0,0 +1,42 @@
1
+ module ProMotion::MotionTable
2
+ module RefreshableTable
3
+ def make_refreshable(params={})
4
+ pull_message = params[:pull_message] || "Pull to refresh"
5
+ @refreshing = params[:refreshing] || "Refreshing data..."
6
+ @updated_format = params[:updated_format] || "Last updated at %s"
7
+ @updated_time_format = params[:updated_time_format] || "%l:%M %p"
8
+ @refreshable_callback = params[:callback]|| :on_refresh
9
+
10
+ @refresh_control = UIRefreshControl.alloc.init
11
+ @refresh_control.attributedTitle = NSAttributedString.alloc.initWithString(pull_message)
12
+ @refresh_control.addTarget(self, action:'refreshView:', forControlEvents:UIControlEventValueChanged)
13
+ self.refreshControl = @refresh_control
14
+ end
15
+ alias :makeRefreshable :make_refreshable
16
+
17
+ ######### iOS methods, headless camel case #######
18
+
19
+ # UIRefreshControl Delegates
20
+ def refreshView(refresh)
21
+ refresh.attributedTitle = NSAttributedString.alloc.initWithString(@refreshing)
22
+ if @refreshable_callback && self.respond_to?(@refreshable_callback)
23
+ self.send(@refreshable_callback)
24
+ else
25
+ PM.logger.warn "You must implement the '#{@refreshable_callback}' method in your TableScreen."
26
+ end
27
+ end
28
+
29
+ def start_refreshing
30
+ return unless @refresh_control
31
+
32
+ @refresh_control.beginRefreshing
33
+ end
34
+
35
+ def end_refreshing
36
+ return unless @refresh_control
37
+
38
+ @refresh_control.attributedTitle = NSAttributedString.alloc.initWithString(sprintf(@updated_format, Time.now.strftime(@updated_time_format)))
39
+ @refresh_control.endRefreshing
40
+ end
41
+ end
42
+ end