motion-prime 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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