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.
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);