motion-prime 0.9.9 → 0.9.9.1

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