motion-hybrid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +76 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +115 -0
  7. data/Rakefile +11 -0
  8. data/app/app_delegate.rb +11 -0
  9. data/app/screens/base_screen.rb +3 -0
  10. data/lib/motion-hybrid.rb +20 -0
  11. data/lib/motion-hybrid/concerns/basic_routes.rb +35 -0
  12. data/lib/motion-hybrid/concerns/bridgeable.rb +71 -0
  13. data/lib/motion-hybrid/concerns/navigatable.rb +137 -0
  14. data/lib/motion-hybrid/concerns/presentable.rb +19 -0
  15. data/lib/motion-hybrid/concerns/styleable.rb +48 -0
  16. data/lib/motion-hybrid/concerns/syncable.rb +14 -0
  17. data/lib/motion-hybrid/concerns/transitionable.rb +28 -0
  18. data/lib/motion-hybrid/concerns/updatable.rb +28 -0
  19. data/lib/motion-hybrid/models/bridge.rb +48 -0
  20. data/lib/motion-hybrid/models/request.rb +29 -0
  21. data/lib/motion-hybrid/models/route.rb +23 -0
  22. data/lib/motion-hybrid/models/router.rb +21 -0
  23. data/lib/motion-hybrid/screens/screen.rb +16 -0
  24. data/lib/motion-hybrid/version.rb +3 -0
  25. data/lib/motion-hybrid/views/icon.rb +9 -0
  26. data/lib/motion-hybrid/views/multi_line_header.rb +37 -0
  27. data/lib/motion-hybrid/views/spinner.rb +33 -0
  28. data/lib/motion-hybrid/views/toast.rb +23 -0
  29. data/lib/resources/jquery.motion-hybrid.coffee +30 -0
  30. data/lib/resources/jquery.motion-hybrid.js +61 -0
  31. data/motion-hybrid.gemspec +33 -0
  32. data/resources/Default-568h@2x.png +0 -0
  33. data/resources/web/default.css +3 -0
  34. data/resources/web/index.html +26 -0
  35. data/resources/web/index_2.html +27 -0
  36. data/resources/web/index_3.html +20 -0
  37. data/resources/web/jquery.js +4 -0
  38. data/resources/web/modal.html +29 -0
  39. data/resources/web/modal_2.html +25 -0
  40. data/resources/web/ratchet-theme-ios.css +9 -0
  41. data/resources/web/ratchet.css +9 -0
  42. data/resources/web/refreshable.html +24 -0
  43. data/spec/basic_routes_spec.rb +90 -0
  44. data/vendor/Podfile.lock +33 -0
  45. 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,14 @@
1
+ module MotionHybrid
2
+ module Syncable
3
+ extend MotionSupport::Concern
4
+
5
+ module ClassMethods
6
+ def sync_sessions(&block)
7
+ BW::HTTP.get(root_url) do
8
+ block.call
9
+ end
10
+ end
11
+ end
12
+
13
+ end
14
+ 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,3 @@
1
+ module MotionHybrid
2
+ VERSION = "0.0.1"
3
+ 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);