motion-hybrid 0.0.1
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 +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);
|