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
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.
|
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 = "
|
16
|
-
app.device_family = [:
|
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
@@ -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
|
-
|
13
|
-
|
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
|
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
|
-
|
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 ||=
|
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
|
35
|
-
|
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
|
45
|
-
home(
|
42
|
+
def open_screen(screen, args={})
|
43
|
+
home(screen)
|
46
44
|
open_home_screen
|
47
45
|
end
|
48
|
-
alias :
|
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
|
-
|
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
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|