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.
- 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
|