motion-prime 1.0.3 → 1.0.4
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 +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +1 -1
- data/ROADMAP.md +3 -0
- data/files/Gemfile +1 -1
- data/lib/motion-prime.rb +1 -1
- data/motion-prime/api_client.rb +7 -5
- data/motion-prime/elements/_content_text_mixin.rb +14 -4
- data/motion-prime/elements/_text_mixin.rb +6 -2
- data/motion-prime/elements/base_element.rb +37 -12
- data/motion-prime/elements/button.rb +1 -1
- data/motion-prime/elements/draw.rb +6 -0
- data/motion-prime/elements/draw/_draw_background_mixin.rb +33 -2
- data/motion-prime/elements/draw/image.rb +9 -5
- data/motion-prime/elements/draw/label.rb +1 -1
- data/motion-prime/elements/label.rb +1 -1
- data/motion-prime/helpers/has_normalizer.rb +2 -1
- data/motion-prime/helpers/has_style_options.rb +16 -0
- data/motion-prime/helpers/has_styles.rb +5 -1
- data/motion-prime/models/_association_mixin.rb +17 -2
- data/motion-prime/models/_base_mixin.rb +5 -1
- data/motion-prime/models/_dirty_mixin.rb +1 -1
- data/motion-prime/models/_nano_bag_mixin.rb +18 -5
- data/motion-prime/models/_sync_mixin.rb +3 -3
- data/motion-prime/models/_timestamps_mixin.rb +3 -3
- data/motion-prime/sections/_draw_section_mixin.rb +20 -3
- data/motion-prime/sections/_section_with_container_mixin.rb +1 -1
- data/motion-prime/sections/abstract_collection.rb +6 -2
- data/motion-prime/sections/base_section.rb +6 -6
- data/motion-prime/sections/collection/collection_delegate.rb +6 -4
- data/motion-prime/sections/form/base_field_section.rb +8 -0
- data/motion-prime/sections/page_view.rb +19 -5
- data/motion-prime/sections/page_view/page_view_delegate.rb +19 -6
- data/motion-prime/sections/table/refresh_mixin.rb +1 -1
- data/motion-prime/sections/table/table_delegate.rb +6 -4
- data/motion-prime/styles/base.rb +1 -1
- data/motion-prime/support/consts.rb +6 -1
- data/motion-prime/support/mp_table_view.rb +4 -4
- data/motion-prime/version.rb +1 -1
- data/motion-prime/views/styles.rb +1 -1
- data/motion-prime/views/view_builder.rb +9 -5
- data/motion-prime/views/view_styler.rb +16 -15
- metadata +37 -36
@@ -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)
|
@@ -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
|
-
|
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? &&
|
58
|
-
|
59
|
-
|
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
|
-
|
128
|
-
if
|
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
|
-
|
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
|
-
|
107
|
-
|
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 || {}).
|
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
|
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.
|
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
|
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.
|
379
|
-
raw_options.
|
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.
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
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
|