motion-hybrid 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +76 -0
- data/LICENSE.txt +22 -0
- data/README.md +115 -0
- data/Rakefile +11 -0
- data/app/app_delegate.rb +11 -0
- data/app/screens/base_screen.rb +3 -0
- data/lib/motion-hybrid.rb +20 -0
- data/lib/motion-hybrid/concerns/basic_routes.rb +35 -0
- data/lib/motion-hybrid/concerns/bridgeable.rb +71 -0
- data/lib/motion-hybrid/concerns/navigatable.rb +137 -0
- data/lib/motion-hybrid/concerns/presentable.rb +19 -0
- data/lib/motion-hybrid/concerns/styleable.rb +48 -0
- data/lib/motion-hybrid/concerns/syncable.rb +14 -0
- data/lib/motion-hybrid/concerns/transitionable.rb +28 -0
- data/lib/motion-hybrid/concerns/updatable.rb +28 -0
- data/lib/motion-hybrid/models/bridge.rb +48 -0
- data/lib/motion-hybrid/models/request.rb +29 -0
- data/lib/motion-hybrid/models/route.rb +23 -0
- data/lib/motion-hybrid/models/router.rb +21 -0
- data/lib/motion-hybrid/screens/screen.rb +16 -0
- data/lib/motion-hybrid/version.rb +3 -0
- data/lib/motion-hybrid/views/icon.rb +9 -0
- data/lib/motion-hybrid/views/multi_line_header.rb +37 -0
- data/lib/motion-hybrid/views/spinner.rb +33 -0
- data/lib/motion-hybrid/views/toast.rb +23 -0
- data/lib/resources/jquery.motion-hybrid.coffee +30 -0
- data/lib/resources/jquery.motion-hybrid.js +61 -0
- data/motion-hybrid.gemspec +33 -0
- data/resources/Default-568h@2x.png +0 -0
- data/resources/web/default.css +3 -0
- data/resources/web/index.html +26 -0
- data/resources/web/index_2.html +27 -0
- data/resources/web/index_3.html +20 -0
- data/resources/web/jquery.js +4 -0
- data/resources/web/modal.html +29 -0
- data/resources/web/modal_2.html +25 -0
- data/resources/web/ratchet-theme-ios.css +9 -0
- data/resources/web/ratchet.css +9 -0
- data/resources/web/refreshable.html +24 -0
- data/spec/basic_routes_spec.rb +90 -0
- data/vendor/Podfile.lock +33 -0
- metadata +242 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
module Presentable
|
3
|
+
|
4
|
+
def presented_from?(url)
|
5
|
+
presenter && presenter_url == url
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def presenter_url
|
11
|
+
presenter.url.sub(/\?.+/, '').sub(/#.+/, '')
|
12
|
+
end
|
13
|
+
|
14
|
+
def presenter
|
15
|
+
navigationController.viewControllers.first.parent_screen if navigationController
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
module Styleable
|
3
|
+
|
4
|
+
def on_init
|
5
|
+
super
|
6
|
+
self.title = nil
|
7
|
+
set_style_class if using_freestyle_css?
|
8
|
+
set_webview_options
|
9
|
+
set_tab_bar_options
|
10
|
+
remove_back_button_label
|
11
|
+
end
|
12
|
+
|
13
|
+
def tab_bar=(tab_bar)
|
14
|
+
@tab_bar = tab_bar
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def using_freestyle_css?
|
20
|
+
view.respond_to?(:styleClass)
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_style_class
|
24
|
+
navigationController.navigationBar.styleClass = style_class if nav_bar?
|
25
|
+
view.styleClass = style_class
|
26
|
+
end
|
27
|
+
|
28
|
+
def style_class
|
29
|
+
style_class = self.class.to_s.underscore
|
30
|
+
style_class += ' modal' if modal?
|
31
|
+
style_class
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_webview_options
|
35
|
+
set_attributes webview, keyboard_display_requires_user_action: false, suppresses_incremental_rendering: true, background_color: UIColor.whiteColor
|
36
|
+
set_attributes webview.scrollView, deceleration_rate: 0.999
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_tab_bar_options
|
40
|
+
set_tab_bar_item title: @tab_bar[:title], icon: Icon.new(@tab_bar[:icon], 25) if @tab_bar
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_back_button_label
|
44
|
+
navigationItem.backBarButtonItem = UIBarButtonItem.alloc.initWithTitle("", style: UIBarButtonItemStylePlain, target: nil, action: nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
module Transitionable
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def spinner
|
7
|
+
@spinner ||= Spinner.new(view)
|
8
|
+
end
|
9
|
+
|
10
|
+
def start_transitions
|
11
|
+
@loading = true
|
12
|
+
spinner.hide
|
13
|
+
App.run_after(0.6) do
|
14
|
+
spinner.show if @loading
|
15
|
+
end
|
16
|
+
webview.scrollView.fade_out(duration: 0.2, opacity: 0.5)
|
17
|
+
end
|
18
|
+
|
19
|
+
def end_transitions
|
20
|
+
@loading = false
|
21
|
+
refresher.endRefreshing if refresher
|
22
|
+
spinner.hide
|
23
|
+
webview.scrollView.fade_in(0.2)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
module Updatable
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def reload_dependents
|
7
|
+
@needs_reload = false
|
8
|
+
dependents.map(&:stop)
|
9
|
+
dependents.map(&:reload)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Inefficient, but will do for now
|
13
|
+
def dependents
|
14
|
+
dependents = all_views - [self]
|
15
|
+
dependents = dependents | [parent_screen] if parent_screen
|
16
|
+
dependents
|
17
|
+
end
|
18
|
+
|
19
|
+
def all_views
|
20
|
+
app_delegate.window.rootViewController.viewControllers.map(&:viewControllers).flatten
|
21
|
+
end
|
22
|
+
|
23
|
+
def needs_reload?
|
24
|
+
@needs_reload
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
class Bridge
|
3
|
+
PATH = NSBundle.mainBundle.resourcePath + '/jquery.motion-hybrid.js'
|
4
|
+
JS_LIB = File.open(PATH).read
|
5
|
+
|
6
|
+
def initialize(screen)
|
7
|
+
@screen = screen
|
8
|
+
@screen.evaluate(JS_LIB)
|
9
|
+
end
|
10
|
+
|
11
|
+
def click(target)
|
12
|
+
js_api("clicked('#{target}')")
|
13
|
+
end
|
14
|
+
|
15
|
+
def click_child(parent, child_index)
|
16
|
+
child_index = remap_index(child_index)
|
17
|
+
js_api("clicked('#{parent}', #{child_index})") if child_index > 0
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def bridge_hash
|
23
|
+
@bridge_hash ||= Dish BW::JSON.parse(bridge_json)
|
24
|
+
end
|
25
|
+
|
26
|
+
def bridge_json
|
27
|
+
js_api('getParams()').presence || '{}'
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(method)
|
31
|
+
bridge_hash.send(method)
|
32
|
+
end
|
33
|
+
|
34
|
+
# iOS button order and actual order of buttons on screen are not the same
|
35
|
+
def remap_index(index)
|
36
|
+
if index == nav_bar_right_button.options.length - 1
|
37
|
+
0
|
38
|
+
else
|
39
|
+
index + 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def js_api(command)
|
44
|
+
@screen.evaluate("MotionHybrid.#{command};").to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
class Request
|
3
|
+
|
4
|
+
def initialize(nsurlrequest, type)
|
5
|
+
@nsurlrequest, @type = nsurlrequest, type
|
6
|
+
end
|
7
|
+
|
8
|
+
def url
|
9
|
+
@nsurlrequest.URL.absoluteString
|
10
|
+
end
|
11
|
+
|
12
|
+
def url_without_anchor
|
13
|
+
url.gsub(/#.+/, '')
|
14
|
+
end
|
15
|
+
|
16
|
+
def anchor
|
17
|
+
@nsurlrequest.URL.fragment
|
18
|
+
end
|
19
|
+
|
20
|
+
def http_method
|
21
|
+
@nsurlrequest.HTTPMethod
|
22
|
+
end
|
23
|
+
|
24
|
+
def type
|
25
|
+
@type
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
class Route
|
3
|
+
attr_accessor :block
|
4
|
+
|
5
|
+
def initialize(pattern, &block)
|
6
|
+
@pattern, @block = pattern, block
|
7
|
+
end
|
8
|
+
|
9
|
+
def matches?(request)
|
10
|
+
# PM.logger.debug "#{request.url} <> #{@pattern}"
|
11
|
+
if @pattern.is_a?(Regexp)
|
12
|
+
@pattern =~ request.url
|
13
|
+
elsif @pattern.start_with?('#')
|
14
|
+
@pattern == "##{request.anchor}"
|
15
|
+
elsif @pattern.start_with?('/')
|
16
|
+
Screen.url_for(@pattern) == request.url
|
17
|
+
else
|
18
|
+
@pattern == request.url
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
class Router
|
3
|
+
|
4
|
+
def initialize(screen)
|
5
|
+
@screen = screen
|
6
|
+
end
|
7
|
+
|
8
|
+
def process(request)
|
9
|
+
routes.find do |route|
|
10
|
+
route.matches?(request) && @screen.instance_exec(request, &route.block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def routes
|
17
|
+
@screen.class.routes
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
class Screen < PM::WebScreen
|
3
|
+
|
4
|
+
include Bridgeable
|
5
|
+
include Navigatable
|
6
|
+
include BasicRoutes
|
7
|
+
include Presentable
|
8
|
+
include Styleable
|
9
|
+
include Syncable
|
10
|
+
include Transitionable
|
11
|
+
include Updatable
|
12
|
+
|
13
|
+
attr_accessor :bridge, :refresher
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
class Icon
|
3
|
+
def self.new(name, size, options = {})
|
4
|
+
icon = FAKFontAwesome.send("#{name}IconWithSize", size)
|
5
|
+
icon.addAttribute(NSForegroundColorAttributeName, value: options[:color]) if options[:color]
|
6
|
+
icon.imageWithSize(CGSizeMake(size, size))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
class MultiLineHeader
|
3
|
+
def self.new(title, subtitle)
|
4
|
+
view = UIView.alloc.initWithFrame CGRectMake(0, 0, 200, 44)
|
5
|
+
view.autoresizesSubviews = true
|
6
|
+
view.styleId = 'multi_line_header' if view.respond_to?(:styleId)
|
7
|
+
|
8
|
+
view.addSubview titleView(title)
|
9
|
+
view.addSubview subtitleView(subtitle)
|
10
|
+
|
11
|
+
view.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
|
12
|
+
UIViewAutoresizingFlexibleRightMargin |
|
13
|
+
UIViewAutoresizingFlexibleTopMargin |
|
14
|
+
UIViewAutoresizingFlexibleBottomMargin)
|
15
|
+
|
16
|
+
view
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.titleView(title)
|
20
|
+
titleView = UILabel.alloc.initWithFrame CGRectMake(0, 2, 200, 24)
|
21
|
+
titleView.textAlignment = UITextAlignmentCenter
|
22
|
+
titleView.font = UIFont.boldSystemFontOfSize(13)
|
23
|
+
titleView.text = title
|
24
|
+
titleView.adjustsFontSizeToFitWidth = true
|
25
|
+
titleView
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.subtitleView(subtitle)
|
29
|
+
subtitleView = UILabel.alloc.initWithFrame CGRectMake(0, 20, 200, 44-24)
|
30
|
+
subtitleView.textAlignment = UITextAlignmentCenter
|
31
|
+
subtitleView.font = UIFont.systemFontOfSize(11)
|
32
|
+
subtitleView.text = subtitle
|
33
|
+
subtitleView.adjustsFontSizeToFitWidth = true
|
34
|
+
subtitleView
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
class Spinner
|
3
|
+
|
4
|
+
def initialize(view)
|
5
|
+
@view = view
|
6
|
+
end
|
7
|
+
|
8
|
+
def show
|
9
|
+
show_hud
|
10
|
+
end
|
11
|
+
|
12
|
+
def hide
|
13
|
+
MBProgressHUD.hideHUDForView(@view, animated: false)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def show_hud
|
19
|
+
hud = MBProgressHUD.showHUDAddedTo(@view, animated: true)
|
20
|
+
hud.mode = MBProgressHUDModeCustomView
|
21
|
+
hud.customView = spin_animation
|
22
|
+
hud.removeFromSuperViewOnHide = true
|
23
|
+
hud.opacity = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def spin_animation
|
27
|
+
spin_animation = RTSpinKitView.alloc.initWithStyle(RTSpinKitViewStyleBounce, color: '#323667'.to_color) # TODO: Style from CSS
|
28
|
+
spin_animation.startAnimating
|
29
|
+
spin_animation
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MotionHybrid
|
2
|
+
class Toast
|
3
|
+
TEXT_COLOR = '#3c763d'.to_color
|
4
|
+
BACKGROUND_COLOR = '#dff0d8'.to_color
|
5
|
+
def initialize(title, subtitle = nil)
|
6
|
+
options = {
|
7
|
+
'kCRToastTextKey' => title,
|
8
|
+
'kCRToastFontKey' => UIFont.boldSystemFontOfSize(15),
|
9
|
+
'kCRToastTextColorKey' => TEXT_COLOR,
|
10
|
+
'kCRToastBackgroundColorKey' => BACKGROUND_COLOR,
|
11
|
+
'kCRToastSubtitleFontKey' => UIFont.boldSystemFontOfSize(15),
|
12
|
+
'kCRToastSubtitleTextColorKey' => TEXT_COLOR,
|
13
|
+
'kCRToastTextAlignmentKey' => NSTextAlignmentLeft,
|
14
|
+
'kCRToastSubtitleTextAlignmentKey' => NSTextAlignmentLeft,
|
15
|
+
'kCRToastNotificationTypeKey' => CRToastTypeNavigationBar
|
16
|
+
}
|
17
|
+
|
18
|
+
options.merge!('kCRToastFontKey' => UIFont.systemFontOfSize(13), 'kCRToastSubtitleTextKey' => subtitle, 'kCRToastTimeIntervalKey' => 3) if subtitle.present?
|
19
|
+
|
20
|
+
CRToastManager.showNotificationWithOptions options, completionBlock: nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class window.MotionHybrid
|
2
|
+
@getParams: ->
|
3
|
+
bridge = $('#motion_hybrid_bridge')
|
4
|
+
|
5
|
+
params = {}
|
6
|
+
params.title = bridge.find('h1').text()
|
7
|
+
params.subtitle = bridge.find('h2').text()
|
8
|
+
params.nav_bar_left_button = parseButton bridge.find('#nav_bar_left_button')
|
9
|
+
params.nav_bar_right_button = parseButton bridge.find('#nav_bar_right_button')
|
10
|
+
params.flash = parseFlash bridge.find('.flash')
|
11
|
+
|
12
|
+
params.refreshable = $('[data-refreshable]').length > 0
|
13
|
+
|
14
|
+
JSON.stringify params
|
15
|
+
|
16
|
+
parseButton = (button) ->
|
17
|
+
{ link: button.attr('href'), options: button.children().map(-> this.innerText).get() } if button.length
|
18
|
+
|
19
|
+
parseFlash = (flash) ->
|
20
|
+
{ title: flash.find('h3').text() || flash.text().trim(), subtitle: flash.find('p').text() } if flash.length
|
21
|
+
|
22
|
+
@clicked: (target, childIndex) ->
|
23
|
+
target = $("##{target}")
|
24
|
+
target = target.children() if childIndex
|
25
|
+
target.get(childIndex || 0).click()
|
26
|
+
|
27
|
+
if document.readyState == 'complete'
|
28
|
+
document.location.href = 'motionhybrid://ready'
|
29
|
+
else
|
30
|
+
jQuery -> document.location.href = 'motionhybrid://ready'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
// Generated by CoffeeScript 1.7.1
|
2
|
+
(function() {
|
3
|
+
window.MotionHybrid = (function() {
|
4
|
+
var parseButton, parseFlash;
|
5
|
+
|
6
|
+
function MotionHybrid() {}
|
7
|
+
|
8
|
+
MotionHybrid.getParams = function() {
|
9
|
+
var bridge, params;
|
10
|
+
bridge = $('#motion_hybrid_bridge');
|
11
|
+
params = {};
|
12
|
+
params.title = bridge.find('h1').text();
|
13
|
+
params.subtitle = bridge.find('h2').text();
|
14
|
+
params.nav_bar_left_button = parseButton(bridge.find('#nav_bar_left_button'));
|
15
|
+
params.nav_bar_right_button = parseButton(bridge.find('#nav_bar_right_button'));
|
16
|
+
params.flash = parseFlash(bridge.find('.flash'));
|
17
|
+
params.refreshable = $('[data-refreshable]').length > 0;
|
18
|
+
return JSON.stringify(params);
|
19
|
+
};
|
20
|
+
|
21
|
+
parseButton = function(button) {
|
22
|
+
if (button.length) {
|
23
|
+
return {
|
24
|
+
link: button.attr('href'),
|
25
|
+
options: button.children().map(function() {
|
26
|
+
return this.innerText;
|
27
|
+
}).get()
|
28
|
+
};
|
29
|
+
}
|
30
|
+
};
|
31
|
+
|
32
|
+
parseFlash = function(flash) {
|
33
|
+
if (flash.length) {
|
34
|
+
return {
|
35
|
+
title: flash.find('h3').text() || flash.text().trim(),
|
36
|
+
subtitle: flash.find('p').text()
|
37
|
+
};
|
38
|
+
}
|
39
|
+
};
|
40
|
+
|
41
|
+
MotionHybrid.clicked = function(target, childIndex) {
|
42
|
+
target = $("#" + target);
|
43
|
+
if (childIndex) {
|
44
|
+
target = target.children();
|
45
|
+
}
|
46
|
+
return target.get(childIndex || 0).click();
|
47
|
+
};
|
48
|
+
|
49
|
+
return MotionHybrid;
|
50
|
+
|
51
|
+
})();
|
52
|
+
|
53
|
+
if (document.readyState === 'complete') {
|
54
|
+
document.location.href = 'motionhybrid://ready';
|
55
|
+
} else {
|
56
|
+
jQuery(function() {
|
57
|
+
return document.location.href = 'motionhybrid://ready';
|
58
|
+
});
|
59
|
+
}
|
60
|
+
|
61
|
+
}).call(this);
|