motion-prime 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile.lock +1 -1
  4. data/ROADMAP.md +3 -0
  5. data/files/Gemfile +1 -1
  6. data/lib/motion-prime.rb +1 -1
  7. data/motion-prime/api_client.rb +7 -5
  8. data/motion-prime/elements/_content_text_mixin.rb +14 -4
  9. data/motion-prime/elements/_text_mixin.rb +6 -2
  10. data/motion-prime/elements/base_element.rb +37 -12
  11. data/motion-prime/elements/button.rb +1 -1
  12. data/motion-prime/elements/draw.rb +6 -0
  13. data/motion-prime/elements/draw/_draw_background_mixin.rb +33 -2
  14. data/motion-prime/elements/draw/image.rb +9 -5
  15. data/motion-prime/elements/draw/label.rb +1 -1
  16. data/motion-prime/elements/label.rb +1 -1
  17. data/motion-prime/helpers/has_normalizer.rb +2 -1
  18. data/motion-prime/helpers/has_style_options.rb +16 -0
  19. data/motion-prime/helpers/has_styles.rb +5 -1
  20. data/motion-prime/models/_association_mixin.rb +17 -2
  21. data/motion-prime/models/_base_mixin.rb +5 -1
  22. data/motion-prime/models/_dirty_mixin.rb +1 -1
  23. data/motion-prime/models/_nano_bag_mixin.rb +18 -5
  24. data/motion-prime/models/_sync_mixin.rb +3 -3
  25. data/motion-prime/models/_timestamps_mixin.rb +3 -3
  26. data/motion-prime/sections/_draw_section_mixin.rb +20 -3
  27. data/motion-prime/sections/_section_with_container_mixin.rb +1 -1
  28. data/motion-prime/sections/abstract_collection.rb +6 -2
  29. data/motion-prime/sections/base_section.rb +6 -6
  30. data/motion-prime/sections/collection/collection_delegate.rb +6 -4
  31. data/motion-prime/sections/form/base_field_section.rb +8 -0
  32. data/motion-prime/sections/page_view.rb +19 -5
  33. data/motion-prime/sections/page_view/page_view_delegate.rb +19 -6
  34. data/motion-prime/sections/table/refresh_mixin.rb +1 -1
  35. data/motion-prime/sections/table/table_delegate.rb +6 -4
  36. data/motion-prime/styles/base.rb +1 -1
  37. data/motion-prime/support/consts.rb +6 -1
  38. data/motion-prime/support/mp_table_view.rb +4 -4
  39. data/motion-prime/version.rb +1 -1
  40. data/motion-prime/views/styles.rb +1 -1
  41. data/motion-prime/views/view_builder.rb +9 -5
  42. data/motion-prime/views/view_styler.rb +16 -15
  43. metadata +37 -36
@@ -29,7 +29,8 @@ module MotionPrime
29
29
  end
30
30
  end
31
31
 
32
- def normalize_value(object, receiver)
32
+ def normalize_value(object, receiver = nil)
33
+ receiver ||= self
33
34
  if element?
34
35
  receiver.send(:instance_exec, section || screen, self, &object)
35
36
  else
@@ -0,0 +1,16 @@
1
+ module MotionPrime
2
+ module HasStyleOptions
3
+ def extract_font_from(options, prefix = nil)
4
+ options ||= {}
5
+ return options[:font] if options[:font].present?
6
+
7
+ name_key = [prefix, 'font_name'].compact.join('_').to_sym
8
+ size_key = [prefix, 'font_size'].compact.join('_').to_sym
9
+ if options.slice(size_key, name_key).any?
10
+ font_name = options[name_key] || :system
11
+ font_size = options[size_key] || 14
12
+ font_name.uifont(font_size)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -3,10 +3,14 @@ module MotionPrime
3
3
  def prepare_gradient(options)
4
4
  colors = options[:colors].map(&:uicolor).map(&:cgcolor)
5
5
  locations = options[:locations] if options[:locations]
6
+ type = options[:type].to_s
6
7
 
7
8
  if self.is_a?(ViewStyler)
8
9
  gradient = CAGradientLayer.layer
9
-
10
+ if type == 'horizontal'
11
+ gradient.startPoint = CGPointMake(0, 0.5)
12
+ gradient.endPoint = CGPointMake(1.0, 0.5)
13
+ end
10
14
  gradient.frame = if options[:frame_width]
11
15
  CGRectMake(options[:frame_x].to_f, options[:frame_y].to_f, options[:frame_width].to_f, options[:frame_height].to_f)
12
16
  else
@@ -6,6 +6,15 @@ module MotionPrime
6
6
  @_bags ||= {}
7
7
  end
8
8
 
9
+ def bags_attributes
10
+ # retrieving has_one/has_many bag keys
11
+ self.class._associations.keys.inject({}) do |result, association_name|
12
+ key = :"#{association_name.pluralize}_bag"
13
+ result[key] = self.info[key] if self.info[key].present?
14
+ result
15
+ end
16
+ end
17
+
9
18
  def bag_key_for(bag_name)
10
19
  self.info[bag_name]
11
20
  end
@@ -13,7 +22,7 @@ module MotionPrime
13
22
  # Saves model and all associations to store.
14
23
  #
15
24
  # @return [Prime::Model] model
16
- def save
25
+ def save!
17
26
  _bags.values.each do |bag|
18
27
  bag.store = self.store
19
28
  bag.save
@@ -78,9 +87,10 @@ module MotionPrime
78
87
  value
79
88
  end
80
89
  define_method("#{association_name}_attributes=") do |value|
90
+ bags_attributes = self.send(association_name).try(:bags_attributes) || {}
81
91
  self.send(bag_name).clear
82
-
83
92
  association = association_name.classify.constantize.new
93
+ association.info.merge!(bags_attributes)
84
94
  association.fetch_with_attributes(value)
85
95
  self.send(:"#{bag_name}") << association
86
96
  association
@@ -102,11 +112,16 @@ module MotionPrime
102
112
  self._associations[association_name] = options.merge(type: :many)
103
113
 
104
114
  define_method("#{association_name}_attributes=") do |value|
115
+ bags_attributes = self.send(bag_name).to_a.inject({}) do |result, item|
116
+ result[item.id] = item.bags_attributes if item.id
117
+ result
118
+ end
105
119
  self.send(bag_name).clear
106
120
 
107
121
  pending_save_counter = 0
108
122
  collection = value.inject({}) do |result, attrs|
109
123
  model = association_name.classify.constantize.new
124
+ model.info.merge!(bags_attributes.fetch(attrs[:id], {}))
110
125
  model.fetch_with_attributes(attrs)
111
126
  unique_key = model.id || "pending_#{pending_save_counter+=1}"
112
127
  result.merge(unique_key => model)
@@ -6,10 +6,14 @@ module MotionPrime
6
6
  base.class_attribute :default_sort_options
7
7
  end
8
8
 
9
+ def save
10
+ self.performSelectorOnMainThread :save!, withObject: nil, waitUntilDone: true
11
+ end
12
+
9
13
  # Saves model to default store.
10
14
  #
11
15
  # @return [Prime::Model] model
12
- def save
16
+ def save!
13
17
  set_default_id_if_needed
14
18
  raise StoreError, 'No store provided' unless self.store
15
19
  error_ptr = Pointer.new(:id)
@@ -47,7 +47,7 @@ module MotionPrime
47
47
  end
48
48
  end
49
49
 
50
- def save
50
+ def save!
51
51
  super
52
52
  reset_changed_attributes
53
53
  self
@@ -34,8 +34,9 @@ module MotionPrime
34
34
  # @return self [Prime::Model]
35
35
  def add(object_or_array, options = {})
36
36
  error_ptr = Pointer.new(:id)
37
- options[:existed_ids] ||= []
38
- options[:existed_ids] += filter_array(self.to_a, bag_key: self.key).map(&:id)
37
+ options[:existed_ids] ||= filter_array(self.to_a, bag_key: self.key).inject({}) do |result, item|
38
+ result.merge(item.id => item)
39
+ end
39
40
  prepared = prepare_for_store(object_or_array, options)
40
41
 
41
42
  if object_or_array.is_a?(Array)
@@ -54,9 +55,17 @@ module MotionPrime
54
55
  object.map { |entity| prepare_for_store(entity, options) }.compact
55
56
  else
56
57
  object.bag_key = self.key
57
- if object.id.present? && Array.wrap(options[:existed_ids]).include?(object.id)
58
- return if options[:silent_validation]
59
- raise StoreError, "duplicated item added `#{object.class_name_without_kvo}` with `id` = #{object.id}"
58
+ if object.id.present? && options[:existed_ids].include?(object.id)
59
+ if options[:silent_validation]
60
+ return
61
+ elsif options[:replace]
62
+ replace = options[:existed_ids][object.id]
63
+ replace.delete
64
+ delete_key(replace.key)
65
+ object
66
+ else
67
+ raise StoreError, "duplicated item added `#{object.class_name_without_kvo}` with `id` = #{object.id}"
68
+ end
60
69
  end
61
70
  object
62
71
  end
@@ -117,6 +126,10 @@ module MotionPrime
117
126
  end
118
127
 
119
128
  def save
129
+ self.performSelectorOnMainThread :save!, withObject: nil, waitUntilDone: true
130
+ end
131
+
132
+ def save!
120
133
  self.store ||= MotionPrime::Store.shared_store
121
134
  error_ptr = Pointer.new(:id)
122
135
  result = self.saveAndReturnError(error_ptr)
@@ -124,10 +124,10 @@ module MotionPrime
124
124
 
125
125
  method = options[:method] || (persisted? ? :put : :post)
126
126
  api_client.send(method, url, post_data, options) do |data, status_code|
127
- save_response = !options.has_key?(:save_response) || options[:save_response]
128
- if save_response && status_code.to_s =~ /20\d/ && data.is_a?(Hash)
127
+ assign_response_data = options.fetch(:save_response, true)
128
+ if assign_response_data && status_code.to_s =~ /20\d/ && data.is_a?(Hash)
129
129
  set_attributes_from_response(data)
130
- save
130
+ save if options[:save_response]
131
131
  end
132
132
  block.call(data, status_code, data) if use_callback
133
133
  end
@@ -6,7 +6,7 @@ module MotionPrime
6
6
  base.class_attribute :_timestamp_attributes
7
7
  end
8
8
 
9
- def save
9
+ def save!
10
10
  time = Time.now
11
11
  trigger_timestamp(:save, time)
12
12
  trigger_timestamp(:create, time) if new_record?
@@ -18,13 +18,13 @@ module MotionPrime
18
18
  return unless field
19
19
  self.send(:"#{field}=", time)
20
20
  end
21
-
21
+
22
22
  module ClassMethods
23
23
  def timestamp_attributes(actions = nil)
24
24
  self._timestamp_attributes ||= {}
25
25
  actions ||= {save: :saved_at, create: :created_at}
26
26
  actions.each do |action_name, field|
27
- _timestamp_attributes[action_name.to_sym] = field
27
+ self._timestamp_attributes[action_name.to_sym] = field
28
28
  self.attribute field, type: :time
29
29
  end
30
30
  end
@@ -28,6 +28,14 @@ module MotionPrime
28
28
  end
29
29
  end
30
30
 
31
+ def before_element_render(element)
32
+ return unless element == container_element
33
+ self.container_gesture_recognizers = nil
34
+ elements_to_draw.values.each(&:on_container_render)
35
+ end
36
+
37
+ def after_element_render(element); end
38
+
31
39
  def bind_gesture_on_container_for(element, action, receiver = nil)
32
40
  self.container_gesture_recognizers ||= begin
33
41
  set_container_gesture_recognizer
@@ -87,7 +95,11 @@ module MotionPrime
87
95
  end
88
96
  CGRectContainsPoint(element.computed_frame, point)
89
97
  end
90
- (target[:receiver] || self).send(target[:action], recognizer, target[:element]) if target
98
+ stop_propagation = false
99
+ if target
100
+ stop_propagation = !(target[:receiver] || self).send(target[:action], recognizer, target[:element])
101
+ end
102
+ recognizer.cancelsTouchesInView = stop_propagation
91
103
  end
92
104
 
93
105
  def draw_elements(rect)
@@ -103,8 +115,13 @@ module MotionPrime
103
115
  background_color = options[:background_color].try(:uicolor)
104
116
 
105
117
  if gradient_options = options[:gradient]
106
- start_point = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect))
107
- end_point = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect))
118
+ if gradient_options[:type].to_s == 'horizontal'
119
+ start_point = CGPointMake(0, 0.5)
120
+ end_point = CGPointMake(1.0, 0.5)
121
+ else
122
+ start_point = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect))
123
+ end_point = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect))
124
+ end
108
125
 
109
126
  # CGContextSaveGState(context)
110
127
  CGContextAddRect(context, rect)
@@ -18,7 +18,7 @@ module MotionPrime
18
18
  has_drawn_content: true
19
19
  })
20
20
  container_element_options = self.class.container_element_options.clone
21
- options = (container_element_options || {}).merge(options)
21
+ options = (container_element_options || {}).deep_merge(options)
22
22
  type = options.delete(:type)
23
23
  MotionPrime::BaseElement.factory(type, options)
24
24
  end
@@ -93,7 +93,9 @@ module MotionPrime
93
93
  def reset_collection_data
94
94
  @did_appear = false
95
95
  Array.wrap(@data).flatten.each do |section|
96
- section.container_element.try(:update_options, reuse_identifier: nil)
96
+ next unless element = section.container_element
97
+ element.update_options(reuse_identifier: nil)
98
+ element.view.try(:removeFromSuperview)
97
99
  end
98
100
  @data = nil
99
101
  @data_stamp = nil
@@ -225,7 +227,9 @@ module MotionPrime
225
227
  display_pending_cells
226
228
  end
227
229
 
228
- def scroll_view_did_scroll(scroll)
230
+ def scroll_view_did_scroll(scroll); end
231
+
232
+ def update_pull_to_refresh_after_scroll(scroll)
229
233
  return unless refresh_view = collection_view.try(:pullToRefreshView)
230
234
  return refresh_view.alpha = 1 if refresh_view.state == SVPullToRefreshStateLoading
231
235
 
@@ -199,7 +199,7 @@ module MotionPrime
199
199
 
200
200
  def render(container_options = {}, force = false)
201
201
  force ? create_elements! : create_elements
202
- self.container_options.merge!(container_options)
202
+ self.container_options.deep_merge!(container_options)
203
203
  run_callbacks :render do
204
204
  render!
205
205
  end
@@ -209,12 +209,12 @@ module MotionPrime
209
209
  render_container(container_options) do
210
210
  elements_to_render.each do |key, element|
211
211
  element.render
212
- on_element_render(element)
213
212
  end
214
213
  end
215
214
  end
216
215
 
217
- def on_element_render(element)
216
+ def after_element_render(element)
217
+ super
218
218
  return unless callbacks = elements_callbacks.try(:[], element.name)
219
219
  callbacks.each do |options|
220
220
  options[:method].to_proc.call(options[:target] || self)
@@ -375,15 +375,15 @@ module MotionPrime
375
375
 
376
376
  def compute_container_options!
377
377
  raw_options = {}
378
- raw_options.merge!(self.class.container_options.try(:clone) || {})
379
- raw_options.merge!(options[:container] || {})
378
+ raw_options.deep_merge!(self.class.container_options.try(:clone) || {})
379
+ raw_options.deep_merge!(options[:container] || {})
380
380
  # allow to pass styles as proc
381
381
  normalize_options(raw_options, elements_eval_object, nil, [:styles])
382
382
  @container_options = raw_options # must be here because section_styles may use container_options for custom styles
383
383
 
384
384
  container_options_from_styles = Styles.for(section_styles.values.flatten)[:container] if section_styles
385
385
  if container_options_from_styles.present?
386
- @container_options = container_options_from_styles.merge(@container_options)
386
+ @container_options = container_options_from_styles.deep_merge(@container_options)
387
387
  end
388
388
  normalize_options(@container_options, elements_eval_object)
389
389
  end
@@ -5,13 +5,14 @@ module MotionPrime
5
5
 
6
6
  def initialize(options)
7
7
  self.collection_section = options[:section].try(:weak_ref)
8
+ @_section_info = collection_section.to_s
8
9
  @section_instance = collection_section.to_s
9
10
  end
10
11
 
11
- # def dealloc
12
- # pp 'Deallocating collection_delegate for ', @section_instance
13
- # super
14
- # end
12
+ def dealloc
13
+ Prime.logger.dealloc_message :collection_delegate, @_section_info
14
+ super
15
+ end
15
16
 
16
17
  def numberOfSectionsInCollectionView(table)
17
18
  collection_section.number_of_groups
@@ -45,6 +46,7 @@ module MotionPrime
45
46
 
46
47
  def scrollViewDidScroll(scroll)
47
48
  collection_section.scroll_view_did_scroll(scroll)
49
+ collection_section.update_pull_to_refresh_after_scroll(scroll)
48
50
  end
49
51
 
50
52
  def scrollViewWillBeginDragging(scroll)
@@ -156,6 +156,14 @@ module MotionPrime
156
156
  observing_errors_for.errors.info.slice(*errors_observer_fields).values.flatten
157
157
  end
158
158
 
159
+ def style_suffixes
160
+ suffixes = @options[:style_suffixes]
161
+ suffixes = normalize_value(suffixes, collection_section) if suffixes.is_a?(Proc)
162
+ suffixes = Array.wrap(suffixes).clone
163
+ suffixes << 'with_errors' if has_errors?
164
+ suffixes
165
+ end
166
+
159
167
  def reload_section
160
168
  clear_observers
161
169
  form.hard_reload_cell_section(self)
@@ -27,17 +27,26 @@ module MotionPrime
27
27
  end
28
28
 
29
29
  def set_page(index, animated = false, &block)
30
- block ||= proc{|a|}
31
30
  page = page_for_index(index)
32
- current_index = index_for_page(page_controller.viewControllers.last).to_i
33
- direction = current_index <= index ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse
34
- page_controller.setViewControllers([page], direction: direction, animated: animated, completion: block)
31
+ set_view_controllers([page], animated, &block)
35
32
  end
36
33
 
37
34
  def reload_collection_data
38
- page_controller.setViewControllers(page_controller.viewControllers, direction: UIPageViewControllerNavigationDirectionForward, animated: false, completion: proc{|a|})
35
+ set_view_controllers(page_controller.viewControllers, false)
39
36
  end
40
37
 
38
+ def set_view_controllers(controllers, animated = false, &completion)
39
+ completion ||= proc{|a|}
40
+ index = index_for_page(controllers.last)
41
+ current_index = index_for_page(page_controller.viewControllers.last).to_i
42
+ direction = current_index <= index ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse
43
+ page_controller.setViewControllers(controllers, direction: direction, animated: animated, completion: completion)
44
+ page_did_set(index)
45
+ end
46
+
47
+ def page_did_set(index); end
48
+ def page_will_set(index); end
49
+
41
50
  def add_pages(sections, follow = false)
42
51
  @data += Array.wrap(sections)
43
52
  if follow
@@ -50,6 +59,10 @@ module MotionPrime
50
59
  end
51
60
  end
52
61
 
62
+ def current_page_id
63
+ index_for_page(page_controller.viewControllers.last)
64
+ end
65
+
53
66
  # Delegate
54
67
  def page_for_index(index)
55
68
  return nil if !index || data.length == 0 || index < 0 || index >= data.size
@@ -58,6 +71,7 @@ module MotionPrime
58
71
  @view_controllers[index]
59
72
  else
60
73
  controller = MotionPrime::Screen.new
74
+ controller.parent_screen = self.screen
61
75
  section = data[index]
62
76
  section.screen = controller.weak_ref
63
77
  controller.set_section :main, instance: section
@@ -5,13 +5,14 @@ module MotionPrime
5
5
 
6
6
  def initialize(options)
7
7
  self.collection_section = options[:section].try(:weak_ref)
8
+ @_section_info = collection_section.to_s
8
9
  @section_instance = collection_section.to_s
9
10
  end
10
11
 
11
- # def dealloc
12
- # pp 'Deallocating page_view_delegate for ', @section_instance
13
- # super
14
- # end
12
+ def dealloc
13
+ Prime.logger.dealloc_message :collection_delegate, @_section_info
14
+ super
15
+ end
15
16
 
16
17
  def viewControllerAtIndex(index, storyboard:storyboard)
17
18
  collection_section.page_for_index(index)
@@ -39,7 +40,7 @@ module MotionPrime
39
40
  UIDevice.currentDevice.orientation == UIDeviceOrientationPortraitUpsideDown ||
40
41
  UIDevice.currentDevice.orientation == UIDeviceOrientationUnknown
41
42
  if is_portrait
42
- page_view_controller.setViewControllers([current], direction:UIPageViewControllerNavigationDirectionForward, animated:true, completion:lambda{|a|})
43
+ collection_section.reload_collection_data
43
44
  page_view_controller.doubleSided = false
44
45
  return UIPageViewControllerSpineLocationMin
45
46
  else
@@ -51,10 +52,22 @@ module MotionPrime
51
52
  prev_vc = pageViewController(page_view_controller, viewControllerBeforeViewController: current)
52
53
  viewControllers = [prev_vc, current]
53
54
  end
54
- page_view_controller.setViewControllers(viewControllers, direction:UIPageViewControllerNavigationDirectionForward, animated:true, completion:lambda{|a|})
55
+ collection_section.set_view_controllers(viewControllers, true)
55
56
  page_view_controller.doubleSided = true
56
57
  return UIPageViewControllerSpineLocationMid
57
58
  end
58
59
  end
60
+
61
+ def pageViewController(pvc, didFinishAnimating: finished, previousViewControllers: previous_view_controllers, transitionCompleted: completed)
62
+ if completed
63
+ index = collection_section.index_for_page(collection_section.page_controller.viewControllers.last)
64
+ collection_section.page_did_set(index)
65
+ end
66
+ end
67
+
68
+ def pageViewController(pvc, willTransitionToViewControllers: pending_view_controllers)
69
+ index = collection_section.index_for_page(pending_view_controllers.last)
70
+ collection_section.page_will_set(index)
71
+ end
59
72
  end
60
73
  end