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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 64d08d319ac85d30d54245b42d93a0e0ea25341e
4
+ data.tar.gz: 9dd02ccbb10a99e80fd98f22d6aca8262e54ee51
5
+ SHA512:
6
+ metadata.gz: 366a07d182912214a43f2a3013aa599f8ceaf78324a68bc4f3d644845cdeee49bcddb3a646632cbce1dba66a4b53b76815ad87dcf0b041b2754e53acafc4ed08
7
+ data.tar.gz: a1ca81a7731b41e6a4c32fefbe591c352d20b51c884673f694d7c5d6c769219f641a153475688c6ee7edea11305fcba2cc9330636d30584e9deb0971a020995a
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ .repl_history
2
+ build
3
+ tags
4
+ app/pixate_code.rb
5
+ resources/*.nib
6
+ resources/*.momd
7
+ resources/*.storyboardc
8
+ resources/Settings.bundle/
9
+ .DS_Store
10
+ nbproject
11
+ .redcar
12
+ #*#
13
+ *~
14
+ *.sw[po]
15
+ .eprj
16
+ .sass-cache
17
+ .bundle
18
+ .idea
19
+ vendor/Pods
20
+ vendor/**/build-*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in motion-hybrid.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,76 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ motion-hybrid (0.0.1)
5
+ ProMotion
6
+ bubble-wrap
7
+ dish
8
+ motion-cocoapods
9
+ motion-require
10
+ motion-support
11
+ sugarcube
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ ProMotion (1.2.0)
17
+ methadone (~> 1.3)
18
+ motion-require (~> 0.2)
19
+ activesupport (3.2.17)
20
+ i18n (~> 0.6, >= 0.6.4)
21
+ multi_json (~> 1.0)
22
+ bubble-wrap (1.5.0)
23
+ claide (0.4.0)
24
+ cocoapods (0.29.0)
25
+ activesupport (>= 3.2.15, < 4)
26
+ claide (~> 0.4.0)
27
+ cocoapods-core (= 0.29.0)
28
+ cocoapods-downloader (~> 0.3.0)
29
+ cocoapods-try-release-fix (~> 0.1.1)
30
+ colored (~> 1.2)
31
+ escape (~> 0.0.4)
32
+ json_pure (~> 1.8)
33
+ nap (~> 0.5)
34
+ open4 (~> 1.3)
35
+ xcodeproj (~> 0.14.1)
36
+ cocoapods-core (0.29.0)
37
+ activesupport (>= 3.2.15, < 4)
38
+ fuzzy_match (~> 2.0.4)
39
+ json_pure (~> 1.8)
40
+ nap (~> 0.5)
41
+ cocoapods-downloader (0.3.0)
42
+ cocoapods-try-release-fix (0.1.2)
43
+ colored (1.2)
44
+ dish (0.0.3)
45
+ escape (0.0.4)
46
+ fuzzy_match (2.0.4)
47
+ i18n (0.6.9)
48
+ json_pure (1.8.1)
49
+ methadone (1.3.2)
50
+ bundler
51
+ motion-cocoapods (1.4.0)
52
+ cocoapods (>= 0.26.2)
53
+ motion-redgreen (0.1.0)
54
+ motion-require (0.2.0)
55
+ motion-support (0.2.6)
56
+ motion-require (>= 0.0.6)
57
+ multi_json (1.9.2)
58
+ nap (0.6.0)
59
+ open4 (1.3.3)
60
+ rake (10.1.1)
61
+ sugarcube (1.5.5)
62
+ webstub (1.0.1)
63
+ xcodeproj (0.14.1)
64
+ activesupport (~> 3.0)
65
+ colored (~> 1.2)
66
+ rake
67
+
68
+ PLATFORMS
69
+ ruby
70
+
71
+ DEPENDENCIES
72
+ bundler (~> 1.5)
73
+ motion-hybrid!
74
+ motion-redgreen
75
+ rake
76
+ webstub
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jens Balvig
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # motion-hybrid
2
+
3
+ motion-hybrid takes your existing web app and views and wraps them in a snappy native iOS interface
4
+
5
+ ## Installation
6
+
7
+
8
+ Add to your rubymotion project's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'motion-hybrid'
12
+ ```
13
+
14
+ Run bundle and install required cocoapods:
15
+
16
+ ```bash
17
+ $ bundle
18
+ $ rake pod:install
19
+ ```
20
+
21
+ ## Basic Usage
22
+
23
+ Create a screen that inherits from `MotionHybrid::Screen` and set the base url of your web app:
24
+
25
+ ```ruby
26
+ # app/screens/base_screen.rb
27
+ class BaseScreen < MotionHybrid::Screen
28
+ self.root_url = 'http://github.com'
29
+ end
30
+ ```
31
+
32
+ Create one or more screens and set their paths:
33
+
34
+ ```ruby
35
+ # app/app_delegate.rb
36
+ class AppDelegate < PM::Delegate
37
+ def on_load(app, options)
38
+ BaseScreen.sync_sessions do
39
+ @screen_1 = BaseScreen.new(nav_bar: true, path: '/balvig', tab_bar: { title: 'Balvig', icon: :users })
40
+ @screen_2 = BaseScreen.new(nav_bar: true, path: '/rubymotion', tab-bar: { title: 'Rubymotion', icon: :gear })
41
+ open_tab_bar @screen_1, @screen_2
42
+ end
43
+ end
44
+ end
45
+ ```
46
+
47
+ ### Navigation
48
+
49
+ By default, all GET-links are pushed onto the navigation controller stack.
50
+
51
+ ```html
52
+ <!-- index.html -->
53
+ <a href='index_2.html'>Page 2</a>
54
+
55
+ <!-- index_2.html -->
56
+ <p>This is page 2</p>
57
+ ```
58
+
59
+ <img src='https://dl.dropboxusercontent.com/u/3032793/screenshots/get.gif' width='200px' />
60
+
61
+ ### Modals
62
+
63
+ Links with anchor `#modal` will be opened in a modal window.
64
+ Links _within_ a modal linking to the url of the page that created it will automatically close the modal.
65
+
66
+ ```html
67
+ <!-- index.html -->
68
+ <a href='modal.html#modal'>Page 2</a>
69
+
70
+ <!-- modal.html -->
71
+ <a href='index.html'>This will close the modal</a>
72
+ ```
73
+
74
+ <img src='https://dl.dropboxusercontent.com/u/3032793/screenshots/modal.gif' width='200px' />
75
+
76
+ ### Inline
77
+
78
+ Links with anchor `#self` will open the new url within the current view without pushing a new view on to the stack:
79
+
80
+ ### Non-GET requests
81
+
82
+ Any non-GET requests (form posts etc) will display the result within the current view and also automatically refresh all other views so that pages are up to date.
83
+
84
+ ## The Bridge
85
+
86
+ The bridge is a small javascript connection between the web app and native app that allows you to use HTML in your web page to control pars of the native app.
87
+
88
+ All markup is contained within a div with id `motion-hybrid-bridge`
89
+
90
+ ### Title/subtitles
91
+
92
+ ```html
93
+ <div id='motion-hybrid-bridge'>
94
+ <h1>This is the title</h1>
95
+ <h2>This is a subtitle</h2>
96
+ </div>
97
+ ```
98
+
99
+ ### Alerts
100
+
101
+ ```html
102
+ <div id='motion-hybrid-bridge'>
103
+ <div class='flash'>
104
+ <h3>Congratulations!</h3>
105
+ <p>You completed level 2</p>
106
+ </div>
107
+ </div>
108
+ ```
109
+
110
+ ### Navbar items
111
+
112
+
113
+ ## Custom routes
114
+
115
+ TODO: Write usage instructions here
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ $:.unshift("/Library/RubyMotion/lib")
2
+ require 'motion/project/template/ios'
3
+ require 'motion-hybrid'
4
+ require 'bundler'
5
+ Bundler.require(:development)
6
+
7
+
8
+ Motion::Project::App.setup do |app|
9
+ app.name = 'motion-hybrid test'
10
+ app.version = '1.0'
11
+ end
@@ -0,0 +1,11 @@
1
+ class AppDelegate < PM::Delegate
2
+ def on_load(app, options)
3
+ return true if App.environment == 'test'
4
+
5
+ BaseScreen.sync_sessions do
6
+ @screen_1 = BaseScreen.new(nav_bar: true, path: '/index.html', tab_bar: { title: 'Welcome', icon: :users })
7
+ @screen_2 = BaseScreen.new(nav_bar: true, path: '/refreshable.html', tab_bar: { title: 'Refreshable', icon: :cog })
8
+ open_tab_bar @screen_1, @screen_2
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ class BaseScreen < MotionHybrid::Screen
2
+ self.root_url = NSURL.fileURLWithPath(NSBundle.mainBundle.resourcePath + '/web').to_s
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'bubble-wrap'
2
+ require 'dish/motion'
3
+ require 'motion-cocoapods'
4
+ require 'motion-require'
5
+ require 'motion-support'
6
+ require 'ProMotion'
7
+ require 'sugarcube-classic'
8
+
9
+
10
+ Motion::Require.all(Dir.glob(File.expand_path('../motion-hybrid/**/*.rb', __FILE__)))
11
+
12
+ Motion::Project::App.setup do |app|
13
+ app.resources_dirs << File.join(File.dirname(__FILE__), 'resources')
14
+ app.pods do
15
+ pod 'CRToast', '<0.0.6'
16
+ pod 'FontAwesomeKit'
17
+ pod 'MBProgressHUD'
18
+ pod 'SpinKit'
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ module MotionHybrid
2
+ module BasicRoutes
3
+ extend MotionSupport::Concern
4
+
5
+ included do
6
+
7
+ # All clicked GET-links are pushed
8
+ route /.*/ do |request|
9
+ push(request.url) if request.http_method == 'GET' && request.type == UIWebViewNavigationTypeLinkClicked
10
+ end
11
+
12
+ # Links with anchor #self are rendered in place
13
+ route '#self' do |request|
14
+ open_url(request.url_without_anchor)
15
+ end
16
+
17
+ # Hides tab bar
18
+ route '#hide_tab_bar' do |request|
19
+ push(request.url_without_anchor, hide_tab_bar: true)
20
+ end
21
+
22
+ # Opens in modal
23
+ route '#modal' do |request|
24
+ push(request.url_without_anchor, modal: true, nav_bar: true)
25
+ end
26
+
27
+ # Modals are closed if they encounter the url from which they were spawned from
28
+ route /.*/ do |request|
29
+ close_screen if presented_from?(request.url)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,71 @@
1
+ module MotionHybrid
2
+ module Bridgeable
3
+
4
+ def on_appear
5
+ super
6
+ if title_blank? && bridge.present?
7
+ set_titles
8
+ else
9
+ @transition_finished = true
10
+ end
11
+ refresher.endRefreshing if refresher #avoids stuck animation
12
+ end
13
+
14
+ private
15
+
16
+ def title_blank?
17
+ title.blank? && navigationItem.titleView.blank?
18
+ end
19
+
20
+ def load_bridge
21
+ self.bridge = Bridge.new(self)
22
+ end
23
+
24
+ def dom_loaded
25
+ set_titles if transition_finished?
26
+ set_buttons
27
+ set_refresher
28
+ render_flash
29
+ end
30
+
31
+ def transition_finished?
32
+ @transition_finished
33
+ end
34
+
35
+ def set_titles
36
+ if bridge.subtitle.present?
37
+ self.navigationItem.titleView = MultiLineHeader.new(bridge.title, bridge.subtitle)
38
+ else
39
+ self.title = bridge.title
40
+ end
41
+ end
42
+
43
+ def set_buttons
44
+ set_nav_bar_left_button nil, system_item: UIBarButtonSystemItemStop, action: 'close_screen' if bridge.nav_bar_left_button.present?
45
+ set_nav_bar_right_button Icon.new(:cog, 18), action: 'on_nav_bar_right_button_click' if bridge.nav_bar_right_button.present?
46
+ end
47
+
48
+ def on_nav_bar_right_button_click
49
+ if bridge.nav_bar_right_button.link
50
+ bridge.click(:nav_bar_right_button)
51
+ else
52
+ UIActionSheet.alert nil, buttons: bridge.nav_bar_right_button.options do |pressed, index|
53
+ bridge.click_child(:nav_bar_right_button, index)
54
+ end
55
+ end
56
+ end
57
+
58
+ def set_refresher
59
+ if bridge.refreshable && !refresher
60
+ self.refresher = UIRefreshControl.alloc.init
61
+ refresher.addTarget(self, action: 'reload', forControlEvents: UIControlEventValueChanged)
62
+ webview.scrollView.addSubview(refresher)
63
+ end
64
+ end
65
+
66
+ def render_flash
67
+ Toast.new(bridge.flash.title, bridge.flash.subtitle) if bridge.flash
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,137 @@
1
+ module MotionHybrid
2
+ module Navigatable
3
+ extend MotionSupport::Concern
4
+
5
+ included do
6
+ class_attribute :root_url, :routes
7
+ self.routes = []
8
+ end
9
+
10
+ def set_initial_content
11
+ super if @initial_url
12
+ end
13
+
14
+ def content
15
+ NSURL.URLWithString(@initial_url)
16
+ end
17
+
18
+ def path=(path)
19
+ self.url = self.class.url_for(path)
20
+ end
21
+
22
+ def path
23
+ self.class.path_for(url)
24
+ end
25
+
26
+ def url=(url)
27
+ @url = url
28
+ @initial_url ||= url
29
+ open_url(url) if webview
30
+ end
31
+
32
+ def url
33
+ @url
34
+ end
35
+
36
+ def load_started
37
+ start_transitions
38
+ end
39
+
40
+ def load_finished
41
+ @url = current_url
42
+ load_bridge
43
+ reload_dependents if needs_reload?
44
+ end_transitions
45
+ end
46
+
47
+ def load_failed(error)
48
+ unless [102, -999].include?(error.code) #http://stackoverflow.com/questions/19487330/failed-to-load-webpage-error-nsurlerrordomain-error-999
49
+ end_transitions
50
+ show_error(error)
51
+ end
52
+ end
53
+
54
+ def close_screen
55
+ reload_dependents if needs_reload?
56
+ super
57
+ end
58
+
59
+ def reset!
60
+ dismissViewControllerAnimated(false, completion: nil)
61
+ navigation_controller.popToRootViewControllerAnimated(false)
62
+ load_initial_url
63
+ end
64
+
65
+ def on_request(nsurlrequest, type)
66
+ process_request Request.new(nsurlrequest, type)
67
+ end
68
+
69
+ def navigate(new_path)
70
+ return if path == new_path
71
+ process_request Request.new(self.class.request_for(new_path), UIWebViewNavigationTypeLinkClicked)
72
+ end
73
+
74
+ private
75
+
76
+ def process_request(request)
77
+ return dom_loaded && false if request.url == 'motionhybrid://ready'
78
+ @needs_reload = true if request.http_method != 'GET'
79
+
80
+ if router.process(request)
81
+ false
82
+ else
83
+ PM.logger.info("#{self} #{request.http_method} #{request.url}")
84
+ true
85
+ end
86
+ end
87
+
88
+ def push(url, options = {})
89
+ view_options = options.slice!(:hide_tab_bar)
90
+ options[:modal] = view_options[:modal]
91
+ view_options.reverse_merge!(url: url, modal: modal?, transition_style: transition_style)
92
+ open self.class.new(view_options), options
93
+ end
94
+
95
+ def load_initial_url
96
+ self.path = self.class.path_for(@initial_url)
97
+ end
98
+
99
+ def transition_style
100
+ modal? ? UIModalTransitionStyleFlipHorizontal : nil
101
+ end
102
+
103
+ def router
104
+ @router ||= Router.new(self)
105
+ end
106
+
107
+ def show_error(error)
108
+ PM.logger.warn error
109
+ BW::UIAlertView.default(title: 'Could not connect', message: error.localizedDescription, buttons: ['Cancel', 'Try Again']) do |alert|
110
+ reset! if alert.clicked_button.index > 0
111
+ end.show
112
+ end
113
+
114
+ module ClassMethods
115
+
116
+ def url_for(path)
117
+ "#{root_url}#{path}"
118
+ end
119
+
120
+ def request_for(path)
121
+ NSURLRequest.requestWithURL NSURL.URLWithString(url_for(path))
122
+ end
123
+
124
+ def path_for(url)
125
+ return if url.blank?
126
+ NSURL.URLWithString(url).path
127
+ end
128
+
129
+ def route(*patterns, &block)
130
+ patterns.each do |pattern|
131
+ self.routes = [Route.new(pattern, &block)] + routes
132
+ end
133
+ end
134
+ end
135
+
136
+ end
137
+ end