motion-prime 0.1.0
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.
- data/.gitignore +17 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +83 -0
- data/README.md +67 -0
- data/Rakefile +23 -0
- data/app/app_delegate.rb +5 -0
- data/doc/SECTION.md +7 -0
- data/doc/STYLE.md +39 -0
- data/files/Gemfile +3 -0
- data/files/Rakefile +16 -0
- data/files/app/app_delegate.rb +4 -0
- data/files/app/screens/application_screen.rb +3 -0
- data/lib/motion-prime.rb +14 -0
- data/lib/view_styler.rb +141 -0
- data/motion-prime.gemspec +27 -0
- data/motion-prime/app_delegate.rb +56 -0
- data/motion-prime/elements/base.rb +94 -0
- data/motion-prime/elements/button.rb +7 -0
- data/motion-prime/elements/draw.rb +56 -0
- data/motion-prime/elements/draw/image.rb +43 -0
- data/motion-prime/elements/draw/label.rb +13 -0
- data/motion-prime/elements/image.rb +14 -0
- data/motion-prime/elements/label.rb +20 -0
- data/motion-prime/elements/text_field.rb +7 -0
- data/motion-prime/elements/text_view.rb +7 -0
- data/motion-prime/helpers/has_authorization.rb +10 -0
- data/motion-prime/helpers/has_search_bar.rb +25 -0
- data/motion-prime/models/base.rb +220 -0
- data/motion-prime/screens/_aliases_mixin.rb +32 -0
- data/motion-prime/screens/_base_mixin.rb +119 -0
- data/motion-prime/screens/_navigation_bar_mixin.rb +57 -0
- data/motion-prime/screens/_navigation_mixin.rb +118 -0
- data/motion-prime/screens/_orientations_mixin.rb +39 -0
- data/motion-prime/screens/base_screen.rb +22 -0
- data/motion-prime/screens/sidebar_container_screen.rb +58 -0
- data/motion-prime/sections/base.rb +101 -0
- data/motion-prime/sections/draw.rb +62 -0
- data/motion-prime/sections/form.rb +103 -0
- data/motion-prime/sections/form/base_field_section.rb +26 -0
- data/motion-prime/sections/form/password_field_section.rb +33 -0
- data/motion-prime/sections/form/select_field_section.rb +40 -0
- data/motion-prime/sections/form/string_field_section.rb +32 -0
- data/motion-prime/sections/form/submit_field_section.rb +20 -0
- data/motion-prime/sections/form/text_field_section.rb +33 -0
- data/motion-prime/sections/table.rb +97 -0
- data/motion-prime/sections/table/refresh_mixin.rb +13 -0
- data/motion-prime/styles/forms.rb +93 -0
- data/motion-prime/support/_key_value_store.rb +10 -0
- data/motion-prime/support/dm_button.rb +22 -0
- data/motion-prime/support/dm_cell_with_section.rb +12 -0
- data/motion-prime/support/dm_text_field.rb +30 -0
- data/motion-prime/support/dm_text_view.rb +93 -0
- data/motion-prime/support/dm_view_controller.rb +50 -0
- data/motion-prime/support/dm_view_with_section.rb +11 -0
- data/motion-prime/support/navigation_controller.rb +4 -0
- data/motion-prime/support/ui_search_bar_custom.rb +10 -0
- data/motion-prime/support/ui_view.rb +59 -0
- data/motion-prime/version.rb +3 -0
- data/motion-prime/views/layout.rb +45 -0
- data/motion-prime/views/styles.rb +44 -0
- data/motion-prime/views/view_builder.rb +80 -0
- data/motion-prime/views/view_styler.rb +141 -0
- data/resources/Default-568h@2x.png +0 -0
- data/spec/main_spec.rb +9 -0
- metadata +245 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
motion_require '../helpers/has_authorization'
|
2
|
+
module MotionPrime
|
3
|
+
class BaseSection
|
4
|
+
# MotionPrime::BaseSection is container for Elements.
|
5
|
+
# Sections are located inside Screen and can contain multiple Elements.
|
6
|
+
# On render, each element will be added to parent screen.
|
7
|
+
|
8
|
+
# == Basic Sample
|
9
|
+
# class MySection < MotionPrime::BaseSection
|
10
|
+
# element :title, text: "Hello World"
|
11
|
+
# element :avatar, type: :image, image: 'defaults/avatar.jpg'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
DEFAULT_CONTENT_HEIGHT = 65
|
15
|
+
include ::MotionSupport::Callbacks
|
16
|
+
include MotionPrime::HasAuthorization
|
17
|
+
|
18
|
+
attr_accessor :screen, :model, :name, :options, :elements
|
19
|
+
class_attribute :elements_options, :container_options
|
20
|
+
define_callbacks :render
|
21
|
+
|
22
|
+
def initialize(options = {})
|
23
|
+
@options = options
|
24
|
+
@model = options[:model]
|
25
|
+
@name = options[:name] ||= self.class.name.underscore.gsub(/\_section$/, '')
|
26
|
+
create_elements
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_elements
|
30
|
+
self.elements = {}
|
31
|
+
(self.class.elements_options || {}).each do |key, opts|
|
32
|
+
# we should clone options to prevent overriding options
|
33
|
+
# in next element with same name in another class
|
34
|
+
options = opts.clone
|
35
|
+
options[:section] = self
|
36
|
+
self.elements[key] = MotionPrime::BaseElement.factory(options.delete(:type), options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def render(container_options = {})
|
41
|
+
self.container_options.merge!(container_options)
|
42
|
+
self.screen = WeakRef.new(container_options.delete(:to))
|
43
|
+
run_callbacks :render do
|
44
|
+
render!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def render!
|
49
|
+
elements.each do |key, element|
|
50
|
+
element.render(to: screen)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def element(name)
|
55
|
+
elements[name.to_sym]
|
56
|
+
end
|
57
|
+
|
58
|
+
def view(name)
|
59
|
+
element(name).view
|
60
|
+
end
|
61
|
+
|
62
|
+
def container_options
|
63
|
+
@container_options ||= self.class.container_options.try(:clone) || {}
|
64
|
+
end
|
65
|
+
|
66
|
+
def container_height
|
67
|
+
container_options[:height] || DEFAULT_CONTENT_HEIGHT
|
68
|
+
end
|
69
|
+
|
70
|
+
def container_styles
|
71
|
+
container_options[:styles]
|
72
|
+
end
|
73
|
+
|
74
|
+
def benchmark(name, &block)
|
75
|
+
Benchmark.bm do |x|
|
76
|
+
puts "Benchmark: #{name}"
|
77
|
+
x.report(&block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
def element(name, options = {}, &block)
|
83
|
+
options[:type] ||= :label
|
84
|
+
options[:name] = name
|
85
|
+
options[:block] = block
|
86
|
+
self.elements_options ||= {}
|
87
|
+
self.elements_options[name] = options
|
88
|
+
self.elements_options[name]
|
89
|
+
end
|
90
|
+
def container(options)
|
91
|
+
self.container_options = options
|
92
|
+
end
|
93
|
+
def before_render(method_name)
|
94
|
+
set_callback :render, :before, method_name
|
95
|
+
end
|
96
|
+
def after_render(method_name)
|
97
|
+
set_callback :render, :after, method_name
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
class DrawSection < BaseSection
|
3
|
+
# MotionPrime::DrawSection is container for Elements.
|
4
|
+
# Unlike BaseSection, DrawSection renders elements using drawRect, instead of creating subviews
|
5
|
+
# NOTE: only image and label elements are supported at this moment
|
6
|
+
|
7
|
+
# == Basic Sample
|
8
|
+
# class MySection < MotionPrime::DrawSection
|
9
|
+
# element :title, text: "Hello World"
|
10
|
+
# element :avatar, type: :image, image: 'defaults/avatar.jpg'
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
|
14
|
+
attr_accessor :container_view
|
15
|
+
|
16
|
+
def create_elements
|
17
|
+
self.elements = {}
|
18
|
+
(self.class.elements_options || {}).each do |key, opts|
|
19
|
+
# we should clone options to prevent overriding options
|
20
|
+
# in next element with same name in another class
|
21
|
+
options = opts.clone
|
22
|
+
options[:section] = self
|
23
|
+
self.elements[key] = MotionPrime::DrawElement.factory(options.delete(:type), options)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def render!
|
28
|
+
if container_options[:as].to_s == 'cell'
|
29
|
+
@container_view = screen.add_view DMCellWithSection, {
|
30
|
+
section: self, styles: container_options[:styles],
|
31
|
+
reuse_identifier: container_options[:reuse_identifier]
|
32
|
+
}
|
33
|
+
else
|
34
|
+
@container_view = screen.add_view DMViewWithSection, {
|
35
|
+
section: self, styles: container_options[:styles],
|
36
|
+
width: container_options[:width] || 320,
|
37
|
+
height: container_options[:height] || 100,
|
38
|
+
top: container_options[:top] || 0,
|
39
|
+
left: container_options[:left] || 0
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def draw_in(rect)
|
45
|
+
draw_background(rect)
|
46
|
+
draw_elements(rect)
|
47
|
+
end
|
48
|
+
|
49
|
+
def draw_elements(rect)
|
50
|
+
elements.each do |key, element|
|
51
|
+
element.draw_in(rect)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def draw_background(rect)
|
56
|
+
if container_options[:background_color]
|
57
|
+
container_options[:background_color].uicolor.setFill
|
58
|
+
UIRectFill(rect)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
motion_require './table.rb'
|
2
|
+
module MotionPrime
|
3
|
+
class FormSection < TableSection
|
4
|
+
# MotionPrime::FormSection is container for Field Sections.
|
5
|
+
# Forms are located inside Screen and can contain multiple Field Sections.
|
6
|
+
# On render, each field will be added to parent screen.
|
7
|
+
|
8
|
+
# == Basic Sample
|
9
|
+
# class MyLoginForm < MotionPrime::FormSection
|
10
|
+
# field :email, label: { text: 'E-mail' }, input: { placeholder: 'Your E-mail' }
|
11
|
+
# field :submit, title: 'Login', type: :submit
|
12
|
+
#
|
13
|
+
# def on_submit
|
14
|
+
# email = view("email:input").text
|
15
|
+
# puts "Submitted email: #{email}"
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
|
20
|
+
KEYBOARD_HEIGHT_PORTRAIT = 216
|
21
|
+
KEYBOARD_HEIGHT_LANDSCAPE = 162
|
22
|
+
|
23
|
+
class_attribute :fields_options
|
24
|
+
attr_accessor :table, :fields, :keyboard_visible
|
25
|
+
|
26
|
+
after_render :bind_keyboard_events
|
27
|
+
|
28
|
+
def table_data
|
29
|
+
fields.values
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_table
|
33
|
+
init_form_fields
|
34
|
+
screen.table_view styles: [:base_form, name.to_sym], delegate: self, dataSource: self do |table|
|
35
|
+
self.table = table
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def render_cell(index, table)
|
40
|
+
screen.table_view_cell styles: [:base_form_field, :"#{name}_field"], reuse_identifier: cell_name(table, index) do
|
41
|
+
data[index.row].render(to: screen)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# accepts following syntax to find field element:
|
46
|
+
# element("fieldname:elementname"), e.g. element("email:input")
|
47
|
+
def element(name)
|
48
|
+
field_name, element_name = name.split(':')
|
49
|
+
self.fields[field_name.to_sym].element(element_name.to_sym)
|
50
|
+
end
|
51
|
+
|
52
|
+
def on_edit(field); end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
def field(name, options = {})
|
56
|
+
options[:name] = name
|
57
|
+
options[:type] ||= :string
|
58
|
+
self.fields_options ||= {}
|
59
|
+
self.fields_options[name] = options
|
60
|
+
self.fields_options[name]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_height_with_keyboard
|
65
|
+
return if keyboard_visible
|
66
|
+
self.table.height -= KEYBOARD_HEIGHT_PORTRAIT
|
67
|
+
self.keyboard_visible = true
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_height_without_keyboard
|
71
|
+
return unless keyboard_visible
|
72
|
+
self.table.height += KEYBOARD_HEIGHT_PORTRAIT
|
73
|
+
self.keyboard_visible = false
|
74
|
+
end
|
75
|
+
|
76
|
+
def on_keyboard_show
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_keyboard_hide
|
80
|
+
end
|
81
|
+
|
82
|
+
def bind_keyboard_events
|
83
|
+
NSNotificationCenter.defaultCenter.addObserver self,
|
84
|
+
selector: :on_keyboard_show,
|
85
|
+
name: UIKeyboardDidShowNotification,
|
86
|
+
object: nil
|
87
|
+
NSNotificationCenter.defaultCenter.addObserver self,
|
88
|
+
selector: :on_keyboard_hide,
|
89
|
+
name: UIKeyboardDidHideNotification,
|
90
|
+
object: nil
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def init_form_fields
|
96
|
+
self.fields = {}
|
97
|
+
(self.class.fields_options || []).each do |key, field|
|
98
|
+
klass = "MotionPrime::#{field[:type].classify}FieldSection".constantize
|
99
|
+
self.fields[key] = klass.new(field.merge(form: self))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
class BaseFieldSection < BaseSection
|
3
|
+
attr_accessor :form
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
super
|
7
|
+
@form = WeakRef.new(options.delete(:form))
|
8
|
+
@container_options = options.delete(:container)
|
9
|
+
end
|
10
|
+
|
11
|
+
def form_name
|
12
|
+
form.name
|
13
|
+
end
|
14
|
+
|
15
|
+
def container_options
|
16
|
+
@container_options || super
|
17
|
+
end
|
18
|
+
|
19
|
+
def scroll_to_and_make_visible
|
20
|
+
first_element = elements.first.last
|
21
|
+
path = form.table.indexPathForCell first_element.view.superview
|
22
|
+
form.table.scrollToRowAtIndexPath path,
|
23
|
+
atScrollPosition: UITableViewScrollPositionTop, animated: true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
class PasswordFieldSection < BaseFieldSection
|
3
|
+
element :label, type: :label do
|
4
|
+
{
|
5
|
+
styles: [
|
6
|
+
:base_field_label,
|
7
|
+
:base_password_field_label,
|
8
|
+
:"#{form_name}_field_label",
|
9
|
+
:"#{form_name}_#{name}_field_label"
|
10
|
+
]
|
11
|
+
}.merge(options[:label] || {})
|
12
|
+
end
|
13
|
+
element :input, type: :text_field do
|
14
|
+
{
|
15
|
+
styles: [
|
16
|
+
:base_field_input,
|
17
|
+
:base_password_field_input,
|
18
|
+
:"#{form_name}_field_input",
|
19
|
+
:"#{form_name}_#{name}_field_input"
|
20
|
+
],
|
21
|
+
secureTextEntry: true
|
22
|
+
}.merge(options[:input] || {})
|
23
|
+
end
|
24
|
+
after_render :render_input
|
25
|
+
|
26
|
+
def render_input
|
27
|
+
view(:input).on :editing_did_begin do |view|
|
28
|
+
scroll_to_and_make_visible
|
29
|
+
form.on_edit(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
class SelectFieldSection < BaseFieldSection
|
3
|
+
element :label, type: :label do
|
4
|
+
{
|
5
|
+
styles: [
|
6
|
+
:base_field_label,
|
7
|
+
:base_select_field_label,
|
8
|
+
:"#{form_name}_field_label",
|
9
|
+
:"#{form_name}_#{name}_field_label"
|
10
|
+
]
|
11
|
+
}.merge(options[:label] || {})
|
12
|
+
end
|
13
|
+
element :button, type: :button do
|
14
|
+
{
|
15
|
+
styles: [
|
16
|
+
:base_select_field_button,
|
17
|
+
:"#{form_name}_field_button",
|
18
|
+
:"#{form_name}_#{name}_field_button"
|
19
|
+
],
|
20
|
+
}.merge(options[:button] || {})
|
21
|
+
end
|
22
|
+
element :arrow, type: :image do
|
23
|
+
{
|
24
|
+
styles: [
|
25
|
+
:base_select_field_arrow,
|
26
|
+
:"#{form_name}_field_arrow",
|
27
|
+
:"#{form_name}_#{name}_field_arrow"
|
28
|
+
],
|
29
|
+
}.merge(options[:arrow] || {})
|
30
|
+
end
|
31
|
+
|
32
|
+
after_render :render_button
|
33
|
+
|
34
|
+
def render_button
|
35
|
+
view(:button).on :touch do
|
36
|
+
form.send(options[:action]) if options[:action]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
class StringFieldSection < BaseFieldSection
|
3
|
+
element :label, type: :label do
|
4
|
+
{
|
5
|
+
styles: [
|
6
|
+
:base_field_label,
|
7
|
+
:base_string_field_label,
|
8
|
+
:"#{form_name}_field_label",
|
9
|
+
:"#{form_name}_#{name}_field_label"
|
10
|
+
]
|
11
|
+
}.merge(options[:label] || {})
|
12
|
+
end
|
13
|
+
element :input, type: :text_field do
|
14
|
+
{
|
15
|
+
styles: [
|
16
|
+
:base_field_input,
|
17
|
+
:base_string_field_input,
|
18
|
+
:"#{form_name}_field_input",
|
19
|
+
:"#{form_name}_#{name}_field_input"
|
20
|
+
],
|
21
|
+
}.merge(options[:input] || {})
|
22
|
+
end
|
23
|
+
after_render :render_input
|
24
|
+
|
25
|
+
def render_input
|
26
|
+
view(:input).on :editing_did_begin do |view|
|
27
|
+
scroll_to_and_make_visible
|
28
|
+
form.on_edit(self)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
class SubmitFieldSection < BaseFieldSection
|
3
|
+
element :submit, type: :button do
|
4
|
+
{
|
5
|
+
styles: [
|
6
|
+
:base_submit_button,
|
7
|
+
:"#{form_name}_submit_button",
|
8
|
+
:"#{form_name}_#{name}_button"
|
9
|
+
]
|
10
|
+
}.merge(title: options[:title])
|
11
|
+
end
|
12
|
+
after_render :render_submit
|
13
|
+
|
14
|
+
def render_submit
|
15
|
+
view(:submit).on :touch do
|
16
|
+
form.send(options[:action]) if options[:action]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
class TextFieldSection < BaseFieldSection
|
3
|
+
element :label, type: :label do
|
4
|
+
{
|
5
|
+
styles: [
|
6
|
+
:base_field_label,
|
7
|
+
:base_text_field_label,
|
8
|
+
:"#{form_name}_field_label",
|
9
|
+
:"#{form_name}_#{name}_field_label"
|
10
|
+
]
|
11
|
+
}.merge(options[:label] || {})
|
12
|
+
end
|
13
|
+
element :input, type: :text_view do
|
14
|
+
{
|
15
|
+
styles: [
|
16
|
+
:base_field_input,
|
17
|
+
:base_text_field_input,
|
18
|
+
:"#{form_name}_field_input",
|
19
|
+
:"#{form_name}_#{name}_field_input"
|
20
|
+
],
|
21
|
+
editable: true
|
22
|
+
}.merge(options[:input] || {})
|
23
|
+
end
|
24
|
+
after_render :render_input
|
25
|
+
|
26
|
+
def render_input
|
27
|
+
view(:input).on :editing_did_begin do |view|
|
28
|
+
scroll_to_and_make_visible
|
29
|
+
form.on_edit(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|