motion-prime 0.1.7 → 0.2.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.
Files changed (46) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile.lock +1 -1
  4. data/files/app/config/base.rb +7 -4
  5. data/files/app/styles/sidebar.rb +4 -4
  6. data/lib/motion-prime.rb +1 -0
  7. data/motion-prime/config/base.rb +5 -0
  8. data/motion-prime/elements/_text_height_mixin.rb +4 -4
  9. data/motion-prime/elements/base.rb +11 -5
  10. data/motion-prime/elements/draw.rb +105 -26
  11. data/motion-prime/elements/draw/image.rb +2 -2
  12. data/motion-prime/elements/draw/label.rb +20 -3
  13. data/motion-prime/elements/error_message.rb +23 -0
  14. data/motion-prime/elements/label.rb +8 -0
  15. data/motion-prime/models/association.rb +0 -1
  16. data/motion-prime/models/base.rb +8 -192
  17. data/motion-prime/models/errors.rb +62 -1
  18. data/motion-prime/models/exceptions.rb +3 -0
  19. data/motion-prime/models/model.rb +33 -1
  20. data/motion-prime/models/sync.rb +233 -0
  21. data/motion-prime/screens/_base_mixin.rb +4 -1
  22. data/motion-prime/screens/_navigation_mixin.rb +5 -5
  23. data/motion-prime/sections/base.rb +18 -5
  24. data/motion-prime/sections/form.rb +75 -16
  25. data/motion-prime/sections/form/base_field_section.rb +52 -1
  26. data/motion-prime/sections/form/select_field_section.rb +14 -1
  27. data/motion-prime/sections/form/string_field_section.rb +20 -5
  28. data/motion-prime/sections/form/switch_field_section.rb +33 -0
  29. data/motion-prime/sections/form/table_field_section.rb +40 -0
  30. data/motion-prime/sections/form/text_field_section.rb +23 -6
  31. data/motion-prime/sections/table.rb +25 -8
  32. data/motion-prime/styles/base.rb +31 -4
  33. data/motion-prime/support/dm_button.rb +3 -2
  34. data/motion-prime/version.rb +1 -1
  35. data/motion-prime/views/layout.rb +1 -1
  36. data/motion-prime/views/view_builder.rb +82 -66
  37. data/motion-prime/views/view_styler.rb +2 -0
  38. data/resources/fonts/ubuntu.ttf +0 -0
  39. data/spec/helpers/models.rb +1 -1
  40. data/spec/models/association_spec.rb +1 -1
  41. data/spec/models/errors_spec.rb +29 -0
  42. data/spec/models/finder_spec.rb +1 -1
  43. data/spec/models/{base_model_spec.rb → model_spec.rb} +30 -1
  44. data/spec/models/store_extension_spec.rb +1 -1
  45. data/spec/models/store_spec.rb +1 -1
  46. metadata +12 -4
@@ -1,11 +1,53 @@
1
1
  module MotionPrime
2
2
  class BaseFieldSection < BaseSection
3
+ include BW::KVO
3
4
  attr_accessor :form
4
5
 
6
+ after_render :on_section_render
7
+
5
8
  def initialize(options = {})
6
- super
7
9
  @form = options.delete(:form)
10
+ if options[:observe_errors_for]
11
+ @observe_errors_for = self.send(:instance_eval, &options.delete(:observe_errors_for))
12
+ end
13
+ super
8
14
  @container_options = options.delete(:container)
15
+ observe_model_errors
16
+ end
17
+
18
+ def render_element?(element_name)
19
+ case element_name.to_sym
20
+ when :error_message
21
+ @observe_errors_for && @observe_errors_for.errors[name].present?
22
+ when :label
23
+ not @options[:label] === false
24
+ else true
25
+ end
26
+ end
27
+
28
+ def on_section_render
29
+ @status_for_updated = :rendered
30
+ end
31
+
32
+ def observe_model_errors
33
+ return unless @observe_errors_for
34
+ observe @observe_errors_for.errors, name do |old_value, new_value|
35
+ if @status_for_updated == :rendered
36
+ clear_observer_and_reload
37
+ else
38
+ create_elements
39
+ form.table_view.reloadData
40
+ end
41
+ end
42
+ end
43
+
44
+ def clear_observer_and_reload
45
+ unobserve @observe_errors_for.errors, name
46
+ reload_section
47
+ end
48
+
49
+ def build_options_for_element(opts)
50
+ super.merge(observe_errors_for: @observe_errors_for)
9
51
  end
10
52
 
11
53
  def form_name
@@ -56,5 +98,14 @@ module MotionPrime
56
98
  end
57
99
  view(:input).delegate = self.form
58
100
  end
101
+
102
+ def reload_section
103
+ form.reload_cell(self)
104
+ end
105
+
106
+ def container_height
107
+ error_height = element(:error_message).try(:content_height)
108
+ super + error_height.to_i
109
+ end
59
110
  end
60
111
  end
@@ -28,11 +28,24 @@ module MotionPrime
28
28
  ],
29
29
  }.merge(options[:arrow] || {})
30
30
  end
31
+ element :error_message, type: :error_message do
32
+ {
33
+ styles: [
34
+ :base_field_label,
35
+ :base_string_field_label,
36
+ :"#{form_name}_field_label",
37
+ :"#{form_name}_#{name}_field_label",
38
+ :base_error_message
39
+ ],
40
+ hidden: proc { form.model && form.model.errors[name].blank? },
41
+ text: proc { form.model and form.model.errors[name].join("\n") }
42
+ }
43
+ end
31
44
 
32
45
  after_render :bind_select_button
33
46
 
34
47
  def bind_select_button
35
- view(:button).on :touch do
48
+ view(:button).on :touch_down do
36
49
  form.send(options[:action]) if options[:action]
37
50
  end
38
51
  end
@@ -10,15 +10,30 @@ module MotionPrime
10
10
  ]
11
11
  }.merge(options[:label] || {})
12
12
  end
13
+
13
14
  element :input, type: :text_field do
15
+ styles = [
16
+ :base_field_input,
17
+ :base_string_field_input,
18
+ :"#{form_name}_field_input",
19
+ :"#{form_name}_#{name}_field_input"
20
+ ]
21
+ styles << :base_field_input_with_errors if form.model && form.model.errors[name].present?
22
+ {styles: styles}.merge(options[:input] || {})
23
+ end
24
+
25
+ element :error_message, type: :error_message do
14
26
  {
15
27
  styles: [
16
- :base_field_input,
17
- :base_string_field_input,
18
- :"#{form_name}_field_input",
19
- :"#{form_name}_#{name}_field_input"
28
+ :base_field_label,
29
+ :base_string_field_label,
30
+ :"#{form_name}_field_label",
31
+ :"#{form_name}_#{name}_field_label",
32
+ :base_error_message
20
33
  ],
21
- }.merge(options[:input] || {})
34
+ hidden: proc { form.model && form.model.errors[name].blank? },
35
+ text: proc { form.model and form.model.errors[name].join("\n") }
36
+ }
22
37
  end
23
38
  after_render :bind_text_input
24
39
  end
@@ -0,0 +1,33 @@
1
+ module MotionPrime
2
+ class SwitchFieldSection < BaseFieldSection
3
+ element :label, type: :label do
4
+ {
5
+ styles: [
6
+ :base_field_label,
7
+ :base_switch_label,
8
+ :"#{form_name}_switch_label",
9
+ :"#{form_name}_#{name}_switch_label"
10
+ ]
11
+ }.merge(options[:label] || {})
12
+ end
13
+ element :input, type: :switch do
14
+ {
15
+ styles: [
16
+ :base_field_switch,
17
+ :"#{form_name}_field_switch",
18
+ :"#{form_name}_#{name}_field_switch"
19
+ ]
20
+ }.merge(options[:input] || {})
21
+ end
22
+ element :hint, type: :label do
23
+ {
24
+ styles: [
25
+ :base_field_label,
26
+ :base_switch_hint,
27
+ :"#{form_name}_switch_hint",
28
+ :"#{form_name}_#{name}_switch_hint"
29
+ ]
30
+ }.merge(options[:hint] || {})
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ motion_require '../table.rb'
2
+ module MotionPrime
3
+ class TableFieldSection < TableSection
4
+ attr_accessor :form, :cell_element
5
+
6
+ def initialize(options = {})
7
+ @form = options.delete(:form)
8
+ @container_options = options.delete(:container)
9
+ super
10
+ end
11
+
12
+ def render_table
13
+ @styles ||= []
14
+ @styles += [
15
+ :"#{form.name}_table",
16
+ :"#{form.name}_#{name}_table"]
17
+ super
18
+ end
19
+
20
+ def cell
21
+ cell_element || begin
22
+ first_element = elements.values.first
23
+ first_element.view.superview
24
+ end
25
+ end
26
+
27
+ def table_data
28
+ form.send("#{name}_table_data")
29
+ end
30
+
31
+ def container_height
32
+ form.send("#{name}_height")
33
+ end
34
+
35
+ def on_click(table, index)
36
+ section = data[index.row]
37
+ form.send("#{name}_selected", section)
38
+ end
39
+ end
40
+ end
@@ -11,16 +11,33 @@ module MotionPrime
11
11
  }.merge(options[:label] || {})
12
12
  end
13
13
  element :input, type: :text_view do
14
+ styles = [
15
+ :base_field_input,
16
+ :base_text_field_input,
17
+ :"#{form_name}_field_input",
18
+ :"#{form_name}_#{name}_field_input"
19
+ ]
20
+ styles << :base_field_input_with_errors if form.model && form.model.errors[name].present?
21
+
14
22
  {
15
- styles: [
16
- :base_field_input,
17
- :base_text_field_input,
18
- :"#{form_name}_field_input",
19
- :"#{form_name}_#{name}_field_input"
20
- ],
23
+ styles: styles,
21
24
  editable: true
22
25
  }.merge(options[:input] || {})
23
26
  end
27
+
28
+ element :error_message, type: :error_message do
29
+ {
30
+ styles: [
31
+ :base_field_label,
32
+ :base_string_field_label,
33
+ :"#{form_name}_field_label",
34
+ :"#{form_name}_#{name}_field_label",
35
+ :base_error_message
36
+ ],
37
+ hidden: proc { form.model && form.model.errors[name].blank? },
38
+ text: proc { form.model and form.model.errors[name].join("\n") }
39
+ }
40
+ end
24
41
  after_render :bind_text_input
25
42
  end
26
43
  end
@@ -12,22 +12,36 @@ module MotionPrime
12
12
  end
13
13
 
14
14
  def data
15
- @data ||= begin
16
- table_data
17
- end
15
+ @data ||= table_data
16
+ end
17
+
18
+ def data_stamp_for(id)
19
+ @data_stamp[id.to_i]
20
+ end
21
+
22
+ def set_data_stamp(cell_ids)
23
+ @data_stamp ||= []
24
+ cell_ids.each { |id| @data_stamp[id] = Time.now.to_f }
18
25
  end
19
26
 
20
27
  def reload_data
21
28
  @did_appear = false
22
29
  @data = nil
23
- @data_stamp = Time.now.to_i
30
+ set_data_stamp((0..elements_options.count-1).to_a)
24
31
  table_view.reloadData
25
32
  end
26
33
 
34
+ def table_styles
35
+ styles = [:base_table, name.to_sym]
36
+ styles += @styles if @styles.present?
37
+ styles
38
+ end
39
+
27
40
  def render_table
28
- @data_stamp = Time.now.to_i
41
+ set_data_stamp((0..elements_options.count-1))
42
+
29
43
  self.table_view = screen.table_view(
30
- styles: [:base_table, name.to_sym], delegate: self, data_source: self
44
+ styles: table_styles, delegate: self, data_source: self
31
45
  ).view
32
46
  end
33
47
 
@@ -36,6 +50,9 @@ module MotionPrime
36
50
 
37
51
  # define default styles for cell
38
52
  styles = [:"#{name}_cell"]
53
+ Array.wrap(@styles).each do |table_style|
54
+ styles << :"#{table_style}_cell"
55
+ end
39
56
  if item.respond_to?(:container_styles) && item.container_styles.present?
40
57
  styles += Array.wrap(item.container_styles)
41
58
  end
@@ -67,9 +84,9 @@ module MotionPrime
67
84
  record = data[index.row]
68
85
  if record && record.model &&
69
86
  record.model.respond_to?(:id) && record.model.id.present?
70
- "cell_#{record.model.id}_#{@data_stamp}"
87
+ "cell_#{record.model.id}_#{data_stamp_for(index.row)}"
71
88
  else
72
- "cell_#{index.section}_#{index.row}_#{@data_stamp}"
89
+ "cell_#{index.section}_#{index.row}_#{data_stamp_for(index.row)}"
73
90
  end
74
91
  end
75
92
 
@@ -37,7 +37,7 @@ MotionPrime::Styles.define :base do
37
37
  height: 20,
38
38
  left: 0,
39
39
  right: 0,
40
- font: proc { APP_CONFIG[:css_font_base].uifont(12) },
40
+ font: proc { MotionPrime::Config.font.name.uifont(12) },
41
41
  size_to_fit: true
42
42
 
43
43
  # available options for input:
@@ -50,8 +50,8 @@ MotionPrime::Styles.define :base do
50
50
  border_width: 1,
51
51
  border_color: :gray
52
52
  },
53
- font: proc { APP_CONFIG[:css_font_base].uifont(16) },
54
- placeholder_font: proc { APP_CONFIG[:css_font_base].uifont(16) },
53
+ font: proc { MotionPrime::Config.font.name.uifont(16) },
54
+ placeholder_font: proc { MotionPrime::Config.font.name.uifont(16) },
55
55
  background_color: :white,
56
56
  left: 0,
57
57
  right: 0,
@@ -59,6 +59,12 @@ MotionPrime::Styles.define :base do
59
59
  bottom: 0,
60
60
  padding_top: 4
61
61
 
62
+ style :field_input_with_errors,
63
+ layer: {
64
+ border_color: proc { MotionPrime::Config.color.error }
65
+ },
66
+ text_color: proc { MotionPrime::Config.color.error }
67
+
62
68
  # available options for submit button:
63
69
  # @button_type: :rounded, :custom
64
70
  # @background_color: COLOR
@@ -86,7 +92,7 @@ MotionPrime::Styles.define :base do
86
92
  },
87
93
  title_color: :gray,
88
94
  title_label: {
89
- font: proc { APP_CONFIG[:css_font_base].uifont(16) }
95
+ font: proc { MotionPrime::Config.font.name.uifont(16) }
90
96
  }
91
97
  style :select_field_arrow,
92
98
  image: "images/forms/select_arrow.png",
@@ -105,4 +111,25 @@ MotionPrime::Styles.define :base do
105
111
  width: 300,
106
112
  height: 150,
107
113
  top: 30, left: 0
114
+
115
+ style :error_message,
116
+ top: nil,
117
+ bottom: 0,
118
+ width: 300,
119
+ line_break_mode: :wordwrap,
120
+ number_of_lines: 0,
121
+ text_color: proc { MotionPrime::Config.color.error }
122
+
123
+ style :field_switch,
124
+ top: 10,
125
+ width: 79,
126
+ height: 27,
127
+ right: 0
128
+
129
+ style :switch_label,
130
+ top: 10,
131
+ font: proc { MotionPrime::Config.font.name.uifont(16) }
132
+
133
+ style :switch_hint,
134
+ top: 40
108
135
  end
@@ -1,6 +1,6 @@
1
1
  class DMButton < UIButton
2
2
  include MotionPrime::KeyValueStore
3
- attr_accessor :paddingLeft, :paddingTop, :padding
3
+ attr_accessor :paddingLeft, :paddingRight, :paddingTop, :padding
4
4
 
5
5
  def setTitle(value)
6
6
  setTitle value, forState: UIControlStateNormal
@@ -8,10 +8,11 @@ class DMButton < UIButton
8
8
 
9
9
  def drawPadding(rect)
10
10
  padding_left = self.paddingLeft || self.padding || 5
11
+ padding_right = self.paddingRight || padding_left || 5
11
12
  padding_top = self.paddingTop || self.padding || 0
12
13
  self.setTitleEdgeInsets UIEdgeInsetsMake(
13
14
  padding_top, padding_left,
14
- padding_top, padding_left
15
+ padding_top, padding_right
15
16
  )
16
17
  end
17
18
 
@@ -1,3 +1,3 @@
1
1
  module MotionPrime
2
- VERSION = "0.1.7"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -14,8 +14,8 @@ module MotionPrime
14
14
  options = builder.options.merge(calculate_frame: true, bounds: bounds)
15
15
  view = builder.view
16
16
  view_stack.last.addSubview(view) unless view_stack.empty?
17
-
18
17
  setup(view, options, &block)
18
+ view.on_added if view.respond_to?(:on_added)
19
19
 
20
20
  view
21
21
  end
@@ -8,78 +8,94 @@ module MotionPrime
8
8
  end
9
9
 
10
10
  def view_for_class(klass, root_klass, options = {})
11
- if VIEWS_MAP.key?(klass.name)
12
- VIEWS_MAP[klass.name].call root_klass, options
11
+ if views_map.key?(klass.name)
12
+ views_map[klass.name].call root_klass, options
13
13
  else
14
14
  view_for_class klass.superclass, root_klass, options
15
15
  end
16
16
  end
17
17
 
18
- VIEWS_MAP = {
19
- 'UIView' => Proc.new {|klass, options| klass.alloc.initWithFrame CGRectZero },
20
- 'UIControl' => Proc.new {|klass, options| klass.alloc.init },
21
- 'UIActionSheet' => Proc.new {|klass, options|
22
- title = options.delete(:title) || ''
23
- delegate = options.delete(:delegate)
24
- cancel_button_title = options.delete(:cancel_button_title)
25
- destructive_button_title = options.delete(:destructive_button_title)
26
- other_button_titles = options.delete(:other_button_titles)
18
+ def views_map
19
+ self.class.views_map
20
+ end
21
+
22
+ class << self
23
+ def register(name, &block)
24
+ views_map[name] = block
25
+ end
26
+
27
+ def views_map
28
+ @views_map ||= default_views_map
29
+ end
30
+
31
+ def default_views_map
32
+ {
33
+ 'UIView' => Proc.new {|klass, options| klass.alloc.initWithFrame CGRectZero },
34
+ 'UIControl' => Proc.new {|klass, options| klass.alloc.init },
35
+ 'UIActionSheet' => Proc.new {|klass, options|
36
+ title = options.delete(:title) || ''
37
+ delegate = options.delete(:delegate)
38
+ cancel_button_title = options.delete(:cancel_button_title)
39
+ destructive_button_title = options.delete(:destructive_button_title)
40
+ other_button_titles = options.delete(:other_button_titles)
27
41
 
28
- klass.alloc.initWithTitle title,
29
- delegate: delegate,
30
- cancelButtonTitle: cancel_button_title,
31
- destructiveButtonTitle: destructive_button_title,
32
- otherButtonTitles: other_button_titles, nil
33
- },
34
- 'UIActivityIndicatorView' => Proc.new{|klass, options|
35
- style = options.delete(:style) || :large.uiactivityindicatorstyle
36
- klass.alloc.initWithActivityIndicatorStyle style
37
- },
38
- 'UIButton' => Proc.new{|klass, options|
39
- is_custom_button = options[:background_image] || options[:title_color]
40
- default_button_type = is_custom_button ? :custom : :rounded
41
- button_type = (options.delete(:button_type) || default_button_type).uibuttontype
42
- klass.buttonWithType button_type
43
- },
44
- 'UIImageView' => Proc.new{|klass, options|
45
- image = options.delete(:image)
46
- highlighted_image = options.delete(:highlighted_image)
42
+ klass.alloc.initWithTitle title,
43
+ delegate: delegate,
44
+ cancelButtonTitle: cancel_button_title,
45
+ destructiveButtonTitle: destructive_button_title,
46
+ otherButtonTitles: other_button_titles, nil
47
+ },
48
+ 'UIActivityIndicatorView' => Proc.new{|klass, options|
49
+ style = options.delete(:style) || :large.uiactivityindicatorstyle
50
+ klass.alloc.initWithActivityIndicatorStyle style
51
+ },
52
+ 'UIButton' => Proc.new{|klass, options|
53
+ is_custom_button = options[:background_image] || options[:title_color]
54
+ default_button_type = is_custom_button ? :custom : :rounded
55
+ button_type = (options.delete(:button_type) || default_button_type).uibuttontype
56
+ klass.buttonWithType button_type
57
+ },
58
+ 'UIImageView' => Proc.new{|klass, options|
59
+ image = options.delete(:image)
60
+ highlighted_image = options.delete(:highlighted_image)
47
61
 
48
- if !image.nil? && !highlighted_image.nil?
49
- klass.alloc.initWithImage image.uiimage, highlightedImage: highlighted_image.uiimage
50
- elsif !image.nil?
51
- klass.alloc.initWithImage image.uiimage
52
- else
53
- klass.alloc.initWithFrame CGRectZero
54
- end
55
- },
56
- 'UIProgressView' => Proc.new{|klass, options|
57
- style = options.delete(:style) || UIProgressViewStyleDefault
58
- klass.alloc.initWithProgressViewStyle style
59
- },
60
- 'UISegmentedControl' => Proc.new{|klass, options|
61
- items = options.delete(:items) || []
62
- klass.alloc.initWithItems items
63
- },
64
- 'UITableView' => Proc.new{|klass, options|
65
- style = options.delete(:style) || UITableViewStylePlain
66
- klass.alloc.initWithFrame CGRectZero, style: style
67
- },
68
- 'UITableViewCell' => Proc.new{|klass, options|
69
- style = options.delete(:style) || UITableViewCellStyleDefault
70
- klass.alloc.initWithStyle style, reuseIdentifier: options.delete(:reuse_identifier)
71
- },
72
- 'UISearchBar' => Proc.new{|klass, options|
73
- klass = options[:search_field_background_image] ? UISearchBarCustom : UISearchBar
74
- search_bar = klass.alloc.init
75
- search_bar.autoresizingMask = UIViewAutoresizingFlexibleWidth
76
- search_bar
77
- },
78
- 'GMSMapView' => Proc.new{|klass, options|
79
- camera = GMSCameraPosition.cameraWithLatitude(35.689466, longitude: 139.700196, zoom: 15)
80
- map = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
81
- map
82
- }
83
- }
62
+ if !image.nil? && !highlighted_image.nil?
63
+ klass.alloc.initWithImage image.uiimage, highlightedImage: highlighted_image.uiimage
64
+ elsif !image.nil?
65
+ klass.alloc.initWithImage image.uiimage
66
+ else
67
+ klass.alloc.initWithFrame CGRectZero
68
+ end
69
+ },
70
+ 'UIProgressView' => Proc.new{|klass, options|
71
+ style = options.delete(:style) || UIProgressViewStyleDefault
72
+ klass.alloc.initWithProgressViewStyle style
73
+ },
74
+ 'UISegmentedControl' => Proc.new{|klass, options|
75
+ items = options.delete(:items) || []
76
+ klass.alloc.initWithItems items
77
+ },
78
+ 'UITableView' => Proc.new{|klass, options|
79
+ style = options.delete(:style) || UITableViewStylePlain
80
+ klass.alloc.initWithFrame CGRectZero, style: style
81
+ },
82
+ 'UITableViewCell' => Proc.new{|klass, options|
83
+ style = options.delete(:style) || UITableViewCellStyleDefault
84
+ klass.alloc.initWithStyle style, reuseIdentifier: options.delete(:reuse_identifier)
85
+ },
86
+ 'UISearchBar' => Proc.new{|klass, options|
87
+ klass = options[:search_field_background_image] ? UISearchBarCustom : UISearchBar
88
+ search_bar = klass.alloc.init
89
+ search_bar.autoresizingMask = UIViewAutoresizingFlexibleWidth
90
+ search_bar
91
+ },
92
+ 'GMSMapView' => Proc.new{|klass, options|
93
+ camera = GMSCameraPosition.cameraWithLatitude(35.689466, longitude: 139.700196, zoom: 15)
94
+ map = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
95
+ map
96
+ }
97
+ }
98
+ end
99
+ end
84
100
  end
85
101
  end