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.
- checksums.yaml +8 -8
- data/CHANGELOG.md +8 -2
- data/Gemfile.lock +2 -2
- data/ROADMAP.md +1 -3
- data/files/Gemfile +1 -1
- data/generators/templates/scaffold/table.rb +1 -1
- data/generators/templates/table.rb +1 -1
- data/motion-prime/config/base.rb +2 -2
- data/motion-prime/elements/_content_text_mixin.rb +1 -1
- data/motion-prime/elements/base_element.rb +14 -7
- data/motion-prime/elements/collection_view_cell.rb +7 -0
- data/motion-prime/elements/draw/image.rb +7 -2
- data/motion-prime/elements/table_view_cell.rb +1 -1
- data/motion-prime/helpers/has_normalizer.rb +1 -1
- data/motion-prime/helpers/has_search_bar.rb +1 -1
- data/motion-prime/models/_nano_bag_mixin.rb +1 -1
- data/motion-prime/models/model.rb +8 -8
- data/motion-prime/models/store.rb +1 -1
- data/motion-prime/screens/screen.rb +3 -3
- data/motion-prime/sections/_async_form_mixin.rb +2 -2
- data/motion-prime/sections/_async_table_mixin.rb +7 -7
- data/motion-prime/sections/_cell_section_mixin.rb +16 -11
- data/motion-prime/sections/_draw_section_mixin.rb +1 -0
- data/motion-prime/sections/{__section_with_container_mixin.rb → _section_with_container_mixin.rb} +2 -2
- data/motion-prime/sections/abstract_collection.rb +291 -0
- data/motion-prime/sections/base_section.rb +2 -2
- data/motion-prime/sections/collection/collection_delegate.rb +62 -0
- data/motion-prime/sections/form.rb +22 -18
- data/motion-prime/sections/form/base_field_section.rb +7 -7
- data/motion-prime/sections/form/form_delegate.rb +1 -1
- data/motion-prime/sections/form/form_header_section.rb +1 -1
- data/motion-prime/sections/form/password_field_section.rb +1 -1
- data/motion-prime/sections/form/static_field_section.rb +1 -1
- data/motion-prime/sections/form/string_field_section.rb +1 -1
- data/motion-prime/sections/form/text_field_section.rb +1 -1
- data/motion-prime/sections/grid.rb +92 -0
- data/motion-prime/sections/table.rb +26 -260
- data/motion-prime/sections/table/refresh_mixin.rb +3 -3
- data/motion-prime/sections/table/table_delegate.rb +1 -0
- data/motion-prime/styles/_mixins.rb +1 -1
- data/motion-prime/styles/base.rb +20 -6
- data/motion-prime/styles/form.rb +1 -1
- data/motion-prime/support/_control_content_alignment.rb +39 -0
- data/motion-prime/support/mp_button.rb +5 -13
- data/motion-prime/support/mp_collection_cell_with_section.rb +12 -0
- data/motion-prime/support/{mp_cell_content_view.rb → mp_table_cell_content_view.rb} +0 -0
- data/motion-prime/support/{mp_cell_with_section.rb → mp_table_cell_with_section.rb} +1 -1
- data/motion-prime/support/mp_text_field.rb +24 -18
- data/motion-prime/support/mp_text_view.rb +1 -0
- data/motion-prime/version.rb +1 -1
- data/motion-prime/views/_frame_calculator_mixin.rb +15 -11
- data/motion-prime/views/layout.rb +6 -4
- data/motion-prime/views/view_builder.rb +10 -0
- data/motion-prime/views/view_styler.rb +5 -1
- data/spec/factories/scaffold/sections/tasks/index_table.rb +1 -1
- data/spec/unit/support/filter_mixin_spec.rb +7 -3
- data/spec/unit/support/frame_calculator_mixin_spec.rb +43 -0
- 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 @
|
154
|
+
if @collection_section && !self.is_a?(BaseFieldSection)
|
155
155
|
cell.setNeedsDisplay
|
156
|
-
@
|
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 '
|
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
|
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.
|
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.
|
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
|
124
|
-
current_inset =
|
125
|
-
current_inset.bottom = KEYBOARD_HEIGHT_PORTRAIT + (self.
|
126
|
-
|
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 =
|
131
|
-
current_inset.bottom = self.
|
132
|
-
|
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
|
136
|
-
@
|
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,
|
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
|
-
|
173
|
-
|
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
|
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
|
220
|
+
def async_collection_data(options = {})
|
217
221
|
super
|
218
222
|
self.send :include, Prime::AsyncFormMixin
|
219
223
|
end
|