motion-prime 0.2.1 → 0.3.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 (81) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile.lock +14 -11
  4. data/README.md +8 -11
  5. data/Rakefile +2 -1
  6. data/bin/prime.rb +47 -0
  7. data/doc/FAQ.md +1 -1
  8. data/files/app/app_delegate.rb +1 -1
  9. data/files/app/config/base.rb +8 -4
  10. data/files/app/screens/application_screen.rb +1 -1
  11. data/files/app/screens/sidebar_screen.rb +1 -1
  12. data/files/app/sections/sidebar/action.rb +1 -1
  13. data/files/app/sections/sidebar/table.rb +1 -1
  14. data/files/app/styles/sidebar.rb +5 -5
  15. data/motion-prime.gemspec +1 -0
  16. data/motion-prime/api_client.rb +81 -0
  17. data/motion-prime/app_delegate.rb +22 -5
  18. data/motion-prime/config/base.rb +5 -0
  19. data/motion-prime/core_ext/kernel.rb +5 -0
  20. data/motion-prime/elements/_field_dimensions_mixin.rb +43 -0
  21. data/motion-prime/elements/_text_dimensions_mixin.rb +39 -0
  22. data/motion-prime/elements/base.rb +40 -17
  23. data/motion-prime/elements/button.rb +20 -0
  24. data/motion-prime/elements/draw.rb +2 -2
  25. data/motion-prime/elements/draw/image.rb +4 -2
  26. data/motion-prime/elements/draw/label.rb +1 -1
  27. data/motion-prime/elements/error_message.rb +3 -16
  28. data/motion-prime/elements/label.rb +13 -2
  29. data/motion-prime/elements/text_field.rb +1 -0
  30. data/motion-prime/helpers/cell_section.rb +9 -0
  31. data/motion-prime/helpers/has_authorization.rb +4 -3
  32. data/motion-prime/helpers/has_normalizer.rb +20 -6
  33. data/motion-prime/helpers/has_search_bar.rb +19 -7
  34. data/motion-prime/helpers/has_style_chain_builder.rb +7 -0
  35. data/motion-prime/models/association.rb +22 -9
  36. data/motion-prime/models/association_collection.rb +54 -23
  37. data/motion-prime/models/bag.rb +13 -12
  38. data/motion-prime/models/base.rb +2 -0
  39. data/motion-prime/models/errors.rb +23 -14
  40. data/motion-prime/models/finder.rb +4 -1
  41. data/motion-prime/models/model.rb +25 -5
  42. data/motion-prime/models/store_extension.rb +1 -7
  43. data/motion-prime/models/sync.rb +75 -43
  44. data/motion-prime/mp.rb +4 -0
  45. data/motion-prime/screens/_base_mixin.rb +18 -12
  46. data/motion-prime/screens/_navigation_bar_mixin.rb +15 -6
  47. data/motion-prime/screens/_navigation_mixin.rb +15 -16
  48. data/motion-prime/screens/base_screen.rb +5 -1
  49. data/motion-prime/screens/sidebar_container_screen.rb +37 -22
  50. data/motion-prime/sections/base.rb +82 -16
  51. data/motion-prime/sections/form.rb +144 -26
  52. data/motion-prime/sections/form/base_field_section.rb +62 -29
  53. data/motion-prime/sections/form/base_header_section.rb +27 -0
  54. data/motion-prime/sections/form/date_field_section.rb +2 -17
  55. data/motion-prime/sections/form/password_field_section.rb +3 -17
  56. data/motion-prime/sections/form/select_field_section.rb +4 -35
  57. data/motion-prime/sections/form/string_field_section.rb +3 -29
  58. data/motion-prime/sections/form/submit_field_section.rb +1 -7
  59. data/motion-prime/sections/form/switch_field_section.rb +3 -23
  60. data/motion-prime/sections/form/text_field_section.rb +3 -33
  61. data/motion-prime/sections/form/text_with_button_field_section.rb +4 -40
  62. data/motion-prime/sections/tabbed.rb +25 -5
  63. data/motion-prime/sections/table.rb +86 -22
  64. data/motion-prime/sections/table/refresh_mixin.rb +3 -1
  65. data/motion-prime/styles/base.rb +7 -89
  66. data/motion-prime/styles/form.rb +116 -0
  67. data/motion-prime/support/dm_button.rb +32 -5
  68. data/motion-prime/support/dm_text_field.rb +31 -7
  69. data/motion-prime/support/dm_text_view.rb +6 -3
  70. data/motion-prime/support/dm_view_controller.rb +3 -3
  71. data/motion-prime/support/ui_search_bar_custom.rb +1 -1
  72. data/motion-prime/version.rb +1 -1
  73. data/motion-prime/views/layout.rb +18 -10
  74. data/motion-prime/views/styles.rb +19 -9
  75. data/motion-prime/views/view_builder.rb +18 -2
  76. data/motion-prime/views/view_styler.rb +59 -5
  77. data/spec/models/errors_spec.rb +3 -3
  78. data/travis.sh +3 -2
  79. metadata +28 -5
  80. data/motion-prime/elements/_text_height_mixin.rb +0 -17
  81. data/motion-prime/sections/form/table_field_section.rb +0 -51
@@ -10,4 +10,9 @@ MotionPrime::Config.font.name = "Ubuntu"
10
10
  MotionPrime::Config.color do |color|
11
11
  color.base = 0x424242
12
12
  color.error = 0xef471f
13
+ end
14
+ MotionPrime::Config.api do |api|
15
+ api.base = "http://example.com"
16
+ api.client_id = ""
17
+ api.client_secret = ""
13
18
  end
@@ -0,0 +1,5 @@
1
+ class Kernel
2
+ def class_name_without_kvo
3
+ self.class.name.gsub(/^NSKVONotifying_/, '')
4
+ end
5
+ end
@@ -0,0 +1,43 @@
1
+ module MotionPrime
2
+ module ElementFieldDimensionsMixin
3
+ def text_value
4
+ text = view ? view.text : computed_options[:text].to_s
5
+ text.empty? ? computed_options[:placeholder] : text
6
+ end
7
+
8
+ def font
9
+ computed_options[:font] || :system.uifont
10
+ end
11
+
12
+ def computed_width
13
+ min_width = computed_options[:min_width] || 20
14
+ return min_width if text_value.to_s.empty?
15
+
16
+ padding_left = view.try(:padding_left) || computed_options[:padding_left] || computed_options[:padding] || view_class.constantize::DEFAULT_PADDING_LEFT
17
+ padding_right = view.try(:padding_right) || computed_options[:padding_right] || padding_left
18
+ max_width = computed_options[:max_width] || Float::MAX
19
+
20
+ attributed_text = NSAttributedString.alloc.initWithString(text_value, attributes: {NSFontAttributeName => font })
21
+ rect = attributed_text.boundingRectWithSize([Float::MAX, Float::MAX], options:NSStringDrawingUsesLineFragmentOrigin, context:nil)
22
+
23
+ width = (rect.size.width + padding_left + padding_right).ceil
24
+ [[width, max_width].min, min_width].max
25
+ end
26
+
27
+ def computed_height
28
+ text = view ? view.titleLabel.text : computed_options[:title]
29
+ return 0 if text.blank?
30
+
31
+ width = computed_options[:width]
32
+ font = computed_options[:title_label][:font] || :system.uifont
33
+ raise "Please set element width for height calculation" unless width
34
+
35
+ attributes = {NSFontAttributeName => font }
36
+ attributed_text = NSAttributedString.alloc.initWithString(text, attributes: attributes)
37
+ rect = attributed_text.boundingRectWithSize([width, Float::MAX], options:NSStringDrawingUsesLineFragmentOrigin, context:nil)
38
+
39
+ padding_top = computed_options[:padding_top] || computed_options[:padding] || view.try(:default_padding_top)
40
+ rect.size.height + padding_top*2
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,39 @@
1
+ module MotionPrime
2
+ module ElementTextDimensionsMixin
3
+ def content_height
4
+ text = view.try(:text) || computed_options[:text]
5
+ return 0 if text.blank?
6
+
7
+ width = computed_options[:width]
8
+ font = computed_options[:font] || :system.uifont
9
+ raise "Please set element width for height calculation" unless width
10
+
11
+ attributes = {NSFontAttributeName => font }
12
+ if computed_options[:line_spacing]
13
+ paragrahStyle = NSMutableParagraphStyle.alloc.init
14
+ paragrahStyle.setLineSpacing(computed_options[:line_spacing])
15
+ attributes[NSParagraphStyleAttributeName] = paragrahStyle
16
+ end
17
+ attributed_text = NSAttributedString.alloc.initWithString(computed_options[:text], attributes: attributes)
18
+ rect = attributed_text.boundingRectWithSize([width, Float::MAX], options:NSStringDrawingUsesLineFragmentOrigin, context:nil)
19
+ rect.size.height
20
+ end
21
+
22
+ def content_width
23
+ text = view.try(:text) || computed_options[:text]
24
+ return 0 if text.blank?
25
+
26
+ width = computed_options[:width]
27
+ font = computed_options[:font] || :system.uifont
28
+
29
+ attributed_text = NSAttributedString.alloc.initWithString(computed_options[:text], attributes: {NSFontAttributeName => font })
30
+ rect = attributed_text.boundingRectWithSize([Float::MAX, Float::MAX], options:NSStringDrawingUsesLineFragmentOrigin, context:nil)
31
+
32
+ rect.size.width
33
+ end
34
+
35
+ def content_outer_height
36
+ content_height + computed_inner_top + computed_inner_bottom
37
+ end
38
+ end
39
+ end
@@ -1,4 +1,5 @@
1
1
  motion_require '../helpers/has_normalizer'
2
+ motion_require '../helpers/has_style_chain_builder'
2
3
  module MotionPrime
3
4
  class BaseElement
4
5
  # MotionPrime::BaseElement is container for UIView class elements with options.
@@ -6,20 +7,21 @@ module MotionPrime
6
7
 
7
8
  include ::MotionSupport::Callbacks
8
9
  include HasNormalizer
10
+ include HasStyleChainBuilder
9
11
 
10
12
  attr_accessor :options, :section, :name,
11
13
  :view_class, :view, :view_name, :styles, :screen
12
-
14
+ delegate :observing_errors?, :has_errors?, :errors_observer_fields, :observing_errors_for, to: :section, allow_nil: true
13
15
  define_callbacks :render
14
16
 
15
17
  def initialize(options = {})
16
18
  @options = options
17
19
  @section = options.delete(:section)
18
- @observe_errors_for = options.delete(:observe_errors_for)
20
+
19
21
  @name = options[:name]
20
22
  @block = options.delete(:block)
21
23
  @view_class = options.delete(:view_class) || "UIView"
22
- @view_name = self.class.name.demodulize.underscore.gsub('_element', '')
24
+ @view_name = self.class_name_without_kvo.demodulize.underscore.gsub('_element', '')
23
25
  end
24
26
 
25
27
  def render(options = {}, &block)
@@ -41,10 +43,12 @@ module MotionPrime
41
43
  end
42
44
 
43
45
  def compute_options!
44
- @computed_options = normalize_options(options, section)
45
- compute_block_options
46
- compute_style_options
47
- @computed_options = normalize_options(@computed_options, section)
46
+ @computed_options ||= {}
47
+ block_options = compute_block_options || {}
48
+ compute_style_options(options, block_options)
49
+ @computed_options.merge!(options)
50
+ @computed_options.merge!(block_options)
51
+ normalize_options(@computed_options, section, %w[text font title_label padding padding_left padding_right max_width width left right])
48
52
  end
49
53
 
50
54
  # Compute options sent inside block, e.g.
@@ -52,20 +56,37 @@ module MotionPrime
52
56
  # {name: model.name}
53
57
  # end
54
58
  def compute_block_options
55
- if block = @block
56
- @computed_options.merge!(section.send :instance_eval, &block)
57
- end
59
+ section.send(:instance_exec, self, &@block) if @block
58
60
  end
59
61
 
60
- def compute_style_options
62
+ def compute_style_options(*style_sources)
61
63
  @styles = []
62
- @styles << :"#{section.name}_#{name}" if section.present?
63
- @styles << :"base_#{@view_name}"
64
- if section && @observe_errors_for && @observe_errors_for.errors[section.name].present?
65
- @styles << :"base_#{name}_with_errors"
64
+ base_styles = {common: [], specific: []}
65
+ suffixes = {common: [@view_name.to_sym, name.try(:to_sym)].compact, specific: []}
66
+
67
+ if section
68
+ field_section = section.respond_to?(:section_styles)
69
+ if field_section
70
+ section.section_styles.each { |type, values| base_styles[type] += values }
71
+ end
72
+ if section.respond_to?(:observing_errors?) && observing_errors? && has_errors?
73
+ suffixes[:common] += [:"#{name}_with_errors", :"#{@view_name}_with_errors"]
74
+ end
66
75
  end
67
- custom_styles = @computed_options.delete(:styles)
68
- @styles += [*custom_styles]
76
+
77
+ # common + specific base - common suffixes
78
+ @styles += build_styles_chain(base_styles[:common], suffixes[:common])
79
+ @styles << :"#{section.name}_#{name}" if section
80
+ @styles += build_styles_chain(base_styles[:specific], suffixes[:common])
81
+ # specific base - specific suffixes
82
+ @styles += build_styles_chain(base_styles[:specific], suffixes[:specific])
83
+ @styles << :"#{section.form.name}_field_#{section.name}_#{name}" if field_section
84
+ # custom style (from options or block options)
85
+ custom_styles = style_sources.map do |source|
86
+ normalize_object(source.delete(:styles), section)
87
+ end.compact.flatten
88
+ @styles += custom_styles
89
+ # puts @view_class.to_s + @styles.inspect, ''
69
90
  @computed_options.merge!(style_options)
70
91
  end
71
92
 
@@ -73,6 +94,8 @@ module MotionPrime
73
94
  Styles.for(styles)
74
95
  end
75
96
 
97
+ private
98
+
76
99
  class << self
77
100
  def factory(type, options = {})
78
101
  class_name = "#{type.classify}Element"
@@ -1,7 +1,27 @@
1
1
  module MotionPrime
2
2
  class ButtonElement < BaseElement
3
+ include MotionPrime::ElementFieldDimensionsMixin
4
+
5
+ after_render :size_to_fit
6
+
7
+ def size_to_fit
8
+ if computed_options[:size_to_fit] || style_options[:size_to_fit]
9
+ if computed_options[:width]
10
+ view.setHeight computed_height
11
+ end
12
+ end
13
+ end
14
+
3
15
  def view_class
4
16
  "DMButton"
5
17
  end
18
+
19
+ def text_value
20
+ view.try(:currentTitle) || computed_options[:title]
21
+ end
22
+
23
+ def font
24
+ computed_options[:title_label].try(:[], :font) || :system.uifont
25
+ end
6
26
  end
7
27
  end
@@ -134,8 +134,8 @@ module MotionPrime
134
134
 
135
135
  def reset_computed_values
136
136
  [:left, :top, :right, :bottom, :width, :height].each do |key|
137
- instance_variable_set "@compited_#{key}", nil
138
- instance_variable_set "@compited_inner_#{key}", nil
137
+ instance_variable_set "@computed_#{key}", nil
138
+ instance_variable_set "@computed_inner_#{key}", nil
139
139
  end
140
140
  end
141
141
 
@@ -12,7 +12,7 @@ module MotionPrime
12
12
  )
13
13
  # draw already initialized image
14
14
  if image_data
15
- image_data.drawInRect(image_rect)
15
+ draw_with_layer(image_data, image_rect)
16
16
  # draw image from resources
17
17
  elsif computed_options[:image]
18
18
  self.image_data = computed_options[:image].uiimage
@@ -50,10 +50,12 @@ module MotionPrime
50
50
 
51
51
  if radius = computed_options[:layer][:corner_radius]
52
52
  layer.masksToBounds = true
53
+ k = image.size.width / rect.size.width
54
+ radius = radius * k
53
55
  layer.cornerRadius = radius
54
56
  end
55
57
 
56
- UIGraphicsBeginImageContext(image.size);
58
+ UIGraphicsBeginImageContext(image.size)
57
59
  layer.renderInContext(UIGraphicsGetCurrentContext())
58
60
  image = UIGraphicsGetImageFromCurrentImageContext()
59
61
  UIGraphicsEndImageContext()
@@ -1,7 +1,7 @@
1
1
  motion_require '../draw.rb'
2
2
  module MotionPrime
3
3
  class LabelDrawElement < DrawElement
4
- include MotionPrime::ElementTextHeightMixin
4
+ include MotionPrime::ElementTextDimensionsMixin
5
5
 
6
6
  def draw_in(rect)
7
7
  options = computed_options
@@ -1,23 +1,10 @@
1
+ motion_require './label'
1
2
  module MotionPrime
2
- class ErrorMessageElement < BaseElement
3
- include MotionPrime::ElementTextHeightMixin
4
-
5
- after_render :size_to_fit
3
+ class ErrorMessageElement < LabelElement
4
+ include MotionPrime::ElementTextDimensionsMixin
6
5
 
7
6
  def view_class
8
7
  "UILabel"
9
8
  end
10
-
11
- def size_to_fit
12
- view.size.height = self.content_height
13
- end
14
-
15
- def computed_inner_top
16
- computed_options[:top].to_i
17
- end
18
-
19
- def computed_inner_bottom
20
- computed_options[:bottom].to_i
21
- end
22
9
  end
23
10
  end
@@ -1,12 +1,23 @@
1
1
  module MotionPrime
2
2
  class LabelElement < BaseElement
3
- include MotionPrime::ElementTextHeightMixin
3
+ include MotionPrime::ElementTextDimensionsMixin
4
4
 
5
+ before_render :size_to_fit_if_needed
5
6
  after_render :size_to_fit
6
7
 
7
8
  def size_to_fit
8
9
  if computed_options[:size_to_fit] || style_options[:size_to_fit]
9
- view.sizeToFit
10
+ if computed_options[:width]
11
+ view.setHeight content_height + 2 # TODO maybe set width too as it can be wider
12
+ else
13
+ view.sizeToFit
14
+ end
15
+ end
16
+ end
17
+
18
+ def size_to_fit_if_needed
19
+ if computed_options[:size_to_fit] && computed_options[:width]
20
+ @computed_options[:height_to_fit] = content_height
10
21
  end
11
22
  end
12
23
 
@@ -1,5 +1,6 @@
1
1
  module MotionPrime
2
2
  class TextFieldElement < BaseElement
3
+ include MotionPrime::ElementFieldDimensionsMixin
3
4
  def view_class
4
5
  "DMTextField"
5
6
  end
@@ -0,0 +1,9 @@
1
+ module MotionPrime
2
+ module CellSection
3
+ attr_accessor :cell_view
4
+
5
+ def cell
6
+ cell_view
7
+ end
8
+ end
9
+ end
@@ -1,9 +1,10 @@
1
1
  module MotionPrime
2
2
  module HasAuthorization
3
3
  def current_user
4
- if defined?(User) && User.respond_to?(:current)
5
- @current_user = User.current
6
- end
4
+ App.delegate.current_user
5
+ end
6
+ def update_current_user
7
+ App.delegate.update_current_user
7
8
  end
8
9
  def user_signed_in?
9
10
  current_user.present?
@@ -1,13 +1,27 @@
1
1
  module MotionPrime
2
2
  module HasNormalizer
3
- def normalize_options(options, receiver = nil)
4
- receiver ||= self
3
+ def normalize_options(unordered_options, receiver = nil, order = nil)
4
+ options = if order
5
+ Hash[unordered_options.sort_by { |k,v| order.index(k.to_s).to_i }]
6
+ else
7
+ unordered_options
8
+ end
9
+
5
10
  options.each do |key, option|
6
- options[key] = if option.is_a?(Proc)
7
- receiver.send :instance_eval, &option
8
- else
9
- option
11
+ unordered_options[key] = normalize_object(option, receiver)
12
+ end
13
+ end
14
+
15
+ def normalize_object(object, receiver)
16
+ receiver ||= self
17
+ if object.is_a?(Proc)
18
+ receiver.send(:instance_exec, self, &object)
19
+ elsif object.is_a?(Hash)
20
+ object.inject({}) do |result, (key, nested_object)|
21
+ result.merge(key => normalize_object(nested_object, receiver))
10
22
  end
23
+ else
24
+ object
11
25
  end
12
26
  end
13
27
  end
@@ -1,19 +1,31 @@
1
1
  # This module adds search functionality, to Screen or TableSection
2
2
  module MotionPrime
3
3
  module HasSearchBar
4
- def add_search_bar(&block)
5
- search_bar = create_search_bar
4
+ def add_search_bar(options = {}, &block)
5
+ target = options.delete(:target)
6
+
7
+ search_bar = create_search_bar(options)
6
8
  search_bar.delegate = self
7
- self.table_view.tableHeaderView = search_bar if is_a?(TableSection)
9
+
10
+ if target
11
+ target.addSubview search_bar
12
+ elsif is_a?(TableSection)
13
+ self.table_view.tableHeaderView = search_bar
14
+ end
15
+
8
16
  @search_callback = block
17
+ search_bar
9
18
  rescue
10
- puts "can't add search bar to #{self.class.name}"
19
+ puts "can't add search bar to #{self.class_name_without_kvo}"
11
20
  end
12
21
 
13
- def create_search_bar
14
- name = is_a?(TableSection) ? name : self.class.name.underscore
22
+ def create_search_bar(options = {})
23
+ name = is_a?(TableSection) ? name : self.class_name_without_kvo.underscore
15
24
  screen = is_a?(TableSection) ? self.screen : self
16
- screen.search_bar(styles: [:"base_search_bar", :"#{name}_search_bar"]).view
25
+ options[:styles] ||= []
26
+ options[:styles] += [:"base_search_bar", :"base_#{name}_search_bar"]
27
+
28
+ screen.search_bar(options).view
17
29
  end
18
30
 
19
31
  def searchBar(search_bar, textDidChange: text)
@@ -0,0 +1,7 @@
1
+ module MotionPrime
2
+ module HasStyleChainBuilder
3
+ def build_styles_chain(base_styles, suffixes)
4
+ [*base_styles].uniq.compact.map { |base_style| [*suffixes].uniq.compact.map { |suffix| [base_style, suffix].join('_').to_sym } }.flatten
5
+ end
6
+ end
7
+ end