motion-prime 0.9.9 → 0.9.9.1

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 (58) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +8 -2
  3. data/Gemfile.lock +2 -2
  4. data/ROADMAP.md +1 -3
  5. data/files/Gemfile +1 -1
  6. data/generators/templates/scaffold/table.rb +1 -1
  7. data/generators/templates/table.rb +1 -1
  8. data/motion-prime/config/base.rb +2 -2
  9. data/motion-prime/elements/_content_text_mixin.rb +1 -1
  10. data/motion-prime/elements/base_element.rb +14 -7
  11. data/motion-prime/elements/collection_view_cell.rb +7 -0
  12. data/motion-prime/elements/draw/image.rb +7 -2
  13. data/motion-prime/elements/table_view_cell.rb +1 -1
  14. data/motion-prime/helpers/has_normalizer.rb +1 -1
  15. data/motion-prime/helpers/has_search_bar.rb +1 -1
  16. data/motion-prime/models/_nano_bag_mixin.rb +1 -1
  17. data/motion-prime/models/model.rb +8 -8
  18. data/motion-prime/models/store.rb +1 -1
  19. data/motion-prime/screens/screen.rb +3 -3
  20. data/motion-prime/sections/_async_form_mixin.rb +2 -2
  21. data/motion-prime/sections/_async_table_mixin.rb +7 -7
  22. data/motion-prime/sections/_cell_section_mixin.rb +16 -11
  23. data/motion-prime/sections/_draw_section_mixin.rb +1 -0
  24. data/motion-prime/sections/{__section_with_container_mixin.rb → _section_with_container_mixin.rb} +2 -2
  25. data/motion-prime/sections/abstract_collection.rb +291 -0
  26. data/motion-prime/sections/base_section.rb +2 -2
  27. data/motion-prime/sections/collection/collection_delegate.rb +62 -0
  28. data/motion-prime/sections/form.rb +22 -18
  29. data/motion-prime/sections/form/base_field_section.rb +7 -7
  30. data/motion-prime/sections/form/form_delegate.rb +1 -1
  31. data/motion-prime/sections/form/form_header_section.rb +1 -1
  32. data/motion-prime/sections/form/password_field_section.rb +1 -1
  33. data/motion-prime/sections/form/static_field_section.rb +1 -1
  34. data/motion-prime/sections/form/string_field_section.rb +1 -1
  35. data/motion-prime/sections/form/text_field_section.rb +1 -1
  36. data/motion-prime/sections/grid.rb +92 -0
  37. data/motion-prime/sections/table.rb +26 -260
  38. data/motion-prime/sections/table/refresh_mixin.rb +3 -3
  39. data/motion-prime/sections/table/table_delegate.rb +1 -0
  40. data/motion-prime/styles/_mixins.rb +1 -1
  41. data/motion-prime/styles/base.rb +20 -6
  42. data/motion-prime/styles/form.rb +1 -1
  43. data/motion-prime/support/_control_content_alignment.rb +39 -0
  44. data/motion-prime/support/mp_button.rb +5 -13
  45. data/motion-prime/support/mp_collection_cell_with_section.rb +12 -0
  46. data/motion-prime/support/{mp_cell_content_view.rb → mp_table_cell_content_view.rb} +0 -0
  47. data/motion-prime/support/{mp_cell_with_section.rb → mp_table_cell_with_section.rb} +1 -1
  48. data/motion-prime/support/mp_text_field.rb +24 -18
  49. data/motion-prime/support/mp_text_view.rb +1 -0
  50. data/motion-prime/version.rb +1 -1
  51. data/motion-prime/views/_frame_calculator_mixin.rb +15 -11
  52. data/motion-prime/views/layout.rb +6 -4
  53. data/motion-prime/views/view_builder.rb +10 -0
  54. data/motion-prime/views/view_styler.rb +5 -1
  55. data/spec/factories/scaffold/sections/tasks/index_table.rb +1 -1
  56. data/spec/unit/support/filter_mixin_spec.rb +7 -3
  57. data/spec/unit/support/frame_calculator_mixin_spec.rb +43 -0
  58. metadata +13 -5
@@ -0,0 +1,291 @@
1
+ motion_require 'base_section'
2
+ module MotionPrime
3
+ class AbstractCollectionSection < Section
4
+ include HasStyleChainBuilder
5
+
6
+ attr_accessor :collection_element, :did_appear
7
+ attr_reader :decelerating
8
+
9
+ before_render :render_collection
10
+
11
+ delegate :set_options, :update_options, to: :collection_element, allow_nil: true
12
+
13
+ %w[table_data
14
+ fixed_table_data
15
+ table_view
16
+ reset_table_data
17
+ async_table_data
18
+ reload_table_data
19
+ table_delegate
20
+ table_styles
21
+ table_styles_base
22
+ prepare_table_cell_sections
23
+ table_element_options
24
+ render_table].each do |table_method|
25
+ define_method table_method do |*args|
26
+ Prime.logger.info "##{table_method} is deprecated: #{caller[0]}"
27
+ send(table_method.gsub('table', 'collection'), *args)
28
+ end
29
+ end
30
+
31
+ # Return sections which will be used to render as collection cells.
32
+ #
33
+ # This method should be redefined in your collection section and must return array.
34
+ # @return [Array<Prime::Section>] array of sections
35
+ def collection_data
36
+ @model || []
37
+ end
38
+
39
+ # Returns cached version of collection data
40
+ #
41
+ # @return [Array<Prime::Section>] cached array of sections
42
+ def data
43
+ @data || set_collection_data
44
+ end
45
+
46
+ # IMPORTANT: when you use #map in collection_data,
47
+ # then #dealloc of Prime::Section will not be called to section created on that #map.
48
+ # We did not find yet why this happening, for now just using hack.
49
+ def fixed_collection_data
50
+ collection_data.to_enum.to_a
51
+ end
52
+
53
+ def dealloc
54
+ Prime.logger.dealloc_message :collection, self, self.collection_element.try(:view).to_s
55
+ collection_delegate.clear_delegated
56
+ collection_view.setDataSource nil
57
+ super
58
+ end
59
+
60
+ # Reset all collection data and reload collection view
61
+ #
62
+ # @return [Boolean] true
63
+ def reload_data
64
+ reset_collection_data
65
+ reload_collection_data
66
+ end
67
+
68
+ # Alias for reload_data
69
+ #
70
+ # @return [Boolean] true
71
+ def reload
72
+ reload_data
73
+ end
74
+
75
+ # Reload collection view data
76
+ #
77
+ # @return [Boolean] true
78
+ def reload_collection_data
79
+ collection_view.reloadData
80
+ true
81
+ end
82
+
83
+ # Reload collection view if data was empty before.
84
+ #
85
+ # @return [Boolean] true if reload was happened
86
+ def refresh_if_needed
87
+ @data.nil? ? reload_collection_data : false
88
+ end
89
+
90
+ # Reset all collection data.
91
+ #
92
+ # @return [Boolean] true
93
+ def reset_collection_data
94
+ @did_appear = false
95
+ Array.wrap(@data).each do |section|
96
+ section.container_element.try(:update_options, reuse_identifier: nil)
97
+ end
98
+ @data = nil
99
+ @data_stamp = nil
100
+ true
101
+ end
102
+
103
+ def collection_styles_base
104
+ raise "Implement #collection_styles_base"
105
+ end
106
+
107
+ def collection_styles
108
+ type = collection_styles_base
109
+
110
+ base_styles = Array.wrap(type)
111
+ item_styles = [name.to_sym]
112
+ item_styles += Array.wrap(@styles) if @styles.present?
113
+ {common: base_styles, specific: item_styles}
114
+ end
115
+
116
+ def cell_section_styles(section)
117
+ # type = [`cell`, `header`, `field`]
118
+
119
+ # UserFormSection example: field :email, type: :string
120
+ # form_name = `user`
121
+ # type = `field`
122
+ # field_name = `email`
123
+ # field_type = `string_field`
124
+
125
+ # CategoriesTableSection example: table is a `CategoryTableSection`, cell is a `CategoryTitleSection`, element :icon, type: :image
126
+ # table_name = `categories`
127
+ # type = `cell` (always true)
128
+ # table_cell_section_name = `title`
129
+ type = section.respond_to?(:cell_type) ? section.cell_type : 'cell'
130
+ suffixes = [type]
131
+ if section.is_a?(BaseFieldSection)
132
+ suffixes << section.default_name
133
+ end
134
+
135
+ styles = {}
136
+ # table: base_table_<type>
137
+ # form: base_form_<type>, base_form_<field_type>
138
+ styles[:common] = build_styles_chain(collection_styles[:common], suffixes)
139
+ if section.is_a?(BaseFieldSection)
140
+ # form cell: _<type>_<field_name> = `_field_email`
141
+ suffixes << :"#{type}_#{section.name}" if section.name
142
+ elsif section.respond_to?(:cell_section_name) # cell section came from table
143
+ # table cell: _<table_cell_section_name> = `_title`
144
+ suffixes << section.cell_section_name
145
+ end
146
+ # table: <table_name>_table_<type>, <table_name>_table_<table_cell_section_name> = `categories_table_cell`, `categories_table_title`
147
+ # form: <form_name>_form_<type>, <form_name>_form_<field_type>, user_form_<type>_email = `user_form_field`, `user_form_string_field`, `user_form_field_email`
148
+ styles[:specific] = build_styles_chain(collection_styles[:specific], suffixes)
149
+
150
+ container_options_styles = section.container_options[:styles]
151
+ if container_options_styles.present?
152
+ styles[:specific] += Array.wrap(container_options_styles)
153
+ end
154
+
155
+ styles
156
+ end
157
+
158
+ def collection_element_options
159
+ container_options.slice(:render_target).merge({
160
+ section: self.weak_ref,
161
+ styles: collection_styles.values.flatten,
162
+ delegate: collection_delegate,
163
+ data_source: collection_delegate
164
+ })
165
+ end
166
+
167
+ def render_collection
168
+ raise "Implement #render_collection"
169
+ end
170
+
171
+ def collection_view
172
+ collection_element.try(:view)
173
+ end
174
+
175
+ def hide
176
+ collection_view.try(:hide)
177
+ end
178
+
179
+ def show
180
+ collection_view.try(:show)
181
+ end
182
+
183
+ def render_cell(index)
184
+ raise "Implement #render_cell"
185
+ end
186
+
187
+ def on_cell_render(cell, index); end
188
+ def on_appear; end
189
+ def on_click(index); end
190
+
191
+ def cell_name(index)
192
+ record = cell_section_by_index(index)
193
+ "cell_#{record.object_id}_#{@data_stamp[record.object_id]}"
194
+ end
195
+
196
+ def cell_sections_for_group(section)
197
+ raise "Implement #cell_sections_for_group"
198
+ end
199
+
200
+ def cell_section_by_index(index)
201
+ cell_sections_for_group(index.section)[index.row]
202
+ end
203
+
204
+ def cell_for_index(index)
205
+ cell = cached_cell(index) || render_cell(index)
206
+ # run table view is appeared callback if needed
207
+ if !@did_appear && index.row == cell_sections_for_group(index.section).size - 1
208
+ on_appear
209
+ end
210
+ cell.is_a?(UIView) ? cell : cell.view
211
+ end
212
+
213
+ def height_for_index(index)
214
+ section = cell_section_by_index(index)
215
+ section.create_elements
216
+ section.container_height
217
+ end
218
+
219
+ def scroll_view_will_begin_dragging(scroll)
220
+ @decelerating = true
221
+ end
222
+
223
+ def scroll_view_did_end_decelerating(scroll)
224
+ @decelerating = false
225
+ display_pending_cells
226
+ end
227
+
228
+ def scroll_view_did_scroll(scroll)
229
+ end
230
+
231
+ def scroll_view_did_end_dragging(scroll, willDecelerate: will_decelerate)
232
+ display_pending_cells unless @decelerating = will_decelerate
233
+ end
234
+
235
+ private
236
+ def cached_cell(index)
237
+ end
238
+
239
+ def display_pending_cells
240
+ collection_view.visibleCells.each do |cell_view|
241
+ if cell_view.section && cell_view.section.pending_display
242
+ cell_view.section.display
243
+ end
244
+ end
245
+ end
246
+
247
+ def set_collection_data
248
+ sections = fixed_collection_data
249
+ prepare_collection_cell_sections(sections)
250
+ @data = sections
251
+ reset_data_stamps
252
+ create_section_elements
253
+ @data
254
+ end
255
+
256
+ def prepare_collection_cell_sections(cells)
257
+ Array.wrap(cells.flatten).each do |cell|
258
+ Prime::Config.prime.cell_section.mixins.each do |mixin|
259
+ cell.class.send(:include, mixin) unless (class << cell; self; end).included_modules.include?(mixin)
260
+ end
261
+ cell.screen ||= screen
262
+ cell.collection_section ||= self.weak_ref if cell.respond_to?(:collection_section=)
263
+ end
264
+ end
265
+
266
+ def container_element_options_for(index)
267
+ cell_section = cell_section_by_index(index)
268
+ {
269
+ reuse_identifier: cell_name(index),
270
+ parent_view: collection_view,
271
+ bounds: {height: cell_section.container_height}
272
+ }
273
+ end
274
+
275
+ def set_data_stamp(section_ids)
276
+ @data_stamp ||= {}
277
+ [*section_ids].each do |id|
278
+ @data_stamp[id] = Time.now.to_f
279
+ end
280
+ end
281
+
282
+ def reset_data_stamps
283
+ keys = data.map(&:object_id)
284
+ set_data_stamp(keys)
285
+ end
286
+
287
+ def create_section_elements
288
+ data.flatten.each(&:create_elements)
289
+ end
290
+ end
291
+ end
@@ -151,9 +151,9 @@ module MotionPrime
151
151
  # reload Draw Elements
152
152
  elements_to_draw.values.each(&:update)
153
153
 
154
- if @table && !self.is_a?(BaseFieldSection)
154
+ if @collection_section && !self.is_a?(BaseFieldSection)
155
155
  cell.setNeedsDisplay
156
- @table.reload_table_data
156
+ @collection_section.reload_collection_data
157
157
  end
158
158
  true
159
159
  end
@@ -0,0 +1,62 @@
1
+ module MotionPrime
2
+ class CollectionDelegate
3
+ include DelegateMixin
4
+ attr_accessor :collection_section
5
+
6
+ def initialize(options)
7
+ self.collection_section = options[:section].try(:weak_ref)
8
+ @section_instance = collection_section.to_s
9
+ end
10
+
11
+ # def dealloc
12
+ # pp 'Deallocating collection_delegate for ', @section_instance
13
+ # super
14
+ # end
15
+
16
+ def numberOfSectionsInCollectionView(table)
17
+ collection_section.number_of_groups
18
+ end
19
+
20
+ def collectionView(table, cellForItemAtIndexPath: index)
21
+ cur_call_time = Time.now.to_f
22
+ cur_call_offset = table.contentOffset.y
23
+ if @prev_call_time
24
+ time_delta = cur_call_time - @prev_call_time
25
+ offset_delta = cur_call_offset - @prev_call_offset
26
+ @deceleration_speed = offset_delta/time_delta
27
+ end
28
+ @prev_call_time = cur_call_time
29
+ @prev_call_offset = cur_call_offset
30
+
31
+ collection_section.cell_for_index(index)
32
+ end
33
+
34
+ def collectionView(table, numberOfItemsInSection: group)
35
+ collection_section.number_of_cells_in_group(group)
36
+ end
37
+
38
+ def collectionView(table, heightForItemAtIndexPath: index)
39
+ collection_section.height_for_index(index)
40
+ end
41
+
42
+ def collectionView(table, didSelectItemAtIndexPath:index)
43
+ collection_section.on_click(index)
44
+ end
45
+
46
+ def scrollViewDidScroll(scroll)
47
+ collection_section.scroll_view_did_scroll(scroll)
48
+ end
49
+
50
+ def scrollViewWillBeginDragging(scroll)
51
+ collection_section.scroll_view_will_begin_dragging(scroll)
52
+ end
53
+
54
+ def scrollViewDidEndDecelerating(scroll)
55
+ collection_section.scroll_view_did_end_decelerating(scroll)
56
+ end
57
+
58
+ def scrollViewDidEndDragging(scroll, willDecelerate: will_decelerate)
59
+ collection_section.scroll_view_did_end_dragging(scroll, willDecelerate: will_decelerate)
60
+ end
61
+ end
62
+ end
@@ -1,4 +1,4 @@
1
- motion_require './table.rb'
1
+ motion_require 'table.rb'
2
2
  motion_require '../helpers/has_style_chain_builder'
3
3
  module MotionPrime
4
4
  class FormSection < TableSection
@@ -21,7 +21,7 @@ module MotionPrime
21
21
  class_attribute :fields_options, :text_field_limits, :text_view_limits, :fields_callbacks
22
22
  attr_accessor :fields, :field_indexes, :keyboard_visible, :rendered_views, :grouped_data
23
23
 
24
- def table_data
24
+ def collection_data
25
25
  if has_many_sections?
26
26
  grouped_data.reject(&:nil?)
27
27
  else
@@ -109,31 +109,35 @@ module MotionPrime
109
109
 
110
110
  def set_height_with_keyboard
111
111
  return if keyboard_visible
112
- self.table_view.height -= KEYBOARD_HEIGHT_PORTRAIT
112
+ self.collection_view.height -= KEYBOARD_HEIGHT_PORTRAIT
113
113
  self.keyboard_visible = true
114
114
  end
115
115
 
116
116
  def set_height_without_keyboard
117
117
  return unless keyboard_visible
118
- self.table_view.height += KEYBOARD_HEIGHT_PORTRAIT
118
+ self.collection_view.height += KEYBOARD_HEIGHT_PORTRAIT
119
119
  self.keyboard_visible = false
120
120
  end
121
121
 
122
122
  def keyboard_will_show
123
- return if table_view.contentSize.height + table_view.top <= UIScreen.mainScreen.bounds.size.height - KEYBOARD_HEIGHT_PORTRAIT
124
- current_inset = table_view.contentInset
125
- current_inset.bottom = KEYBOARD_HEIGHT_PORTRAIT + (self.table_element.computed_options[:bottom_content_inset] || 0)
126
- table_view.contentInset = current_inset
123
+ return if collection_view.contentSize.height + collection_view.top <= UIScreen.mainScreen.bounds.size.height - KEYBOARD_HEIGHT_PORTRAIT
124
+ current_inset = collection_view.contentInset
125
+ current_inset.bottom = KEYBOARD_HEIGHT_PORTRAIT + (self.collection_element.computed_options[:bottom_content_inset] || 0)
126
+ collection_view.contentInset = current_inset
127
127
  end
128
128
 
129
129
  def keyboard_will_hide
130
- current_inset = table_view.contentInset
131
- current_inset.bottom = self.table_element.computed_options[:bottom_content_inset] || 0
132
- table_view.contentInset = current_inset
130
+ current_inset = collection_view.contentInset
131
+ current_inset.bottom = self.collection_element.computed_options[:bottom_content_inset] || 0
132
+ collection_view.contentInset = current_inset
133
133
  end
134
134
 
135
- def table_delegate
136
- @table_delegate ||= FormDelegate.new(section: self)
135
+ def collection_delegate
136
+ @collection_delegate ||= FormDelegate.new(section: self)
137
+ end
138
+
139
+ def collection_styles_base
140
+ :base_form
137
141
  end
138
142
 
139
143
  # ALIASES
@@ -154,7 +158,7 @@ module MotionPrime
154
158
 
155
159
  def load_field(field)
156
160
  field_class = class_factory("#{field[:type]}_field_section", true)
157
- field_class.new(field.merge(screen: screen, table: self.weak_ref))
161
+ field_class.new(field.merge(screen: screen, collection_section: self.weak_ref))
158
162
  end
159
163
 
160
164
  def render_field?(name, options)
@@ -169,15 +173,15 @@ module MotionPrime
169
173
  def reload_data
170
174
  @groups_count = nil
171
175
  init_form_fields # must be before resetting to reflect changes on @data
172
- reset_table_data
173
- reload_table_data
176
+ reset_collection_data
177
+ reload_collection_data
174
178
  end
175
179
 
176
180
  def has_many_sections?
177
181
  group_header_options.present? || grouped_data.count > 1
178
182
  end
179
183
 
180
- def render_table
184
+ def render_collection
181
185
  init_form_fields unless self.fields.present?
182
186
  super
183
187
  end
@@ -213,7 +217,7 @@ module MotionPrime
213
217
  subclass.text_view_limits = self.text_view_limits.try(:clone)
214
218
  end
215
219
 
216
- def async_table_data(options = {})
220
+ def async_collection_data(options = {})
217
221
  super
218
222
  self.send :include, Prime::AsyncFormMixin
219
223
  end