solara 0.4.0 → 0.5.0
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/solara/lib/.DS_Store +0 -0
- data/solara/lib/core/.DS_Store +0 -0
- data/solara/lib/core/brands/brand_switcher.rb +58 -1
- data/solara/lib/core/dashboard/brand/BrandDetail.js +34 -2
- data/solara/lib/core/dashboard/brand/BrandDetailController.js +23 -233
- data/solara/lib/core/dashboard/brand/BrandDetailModel.js +13 -5
- data/solara/lib/core/dashboard/brand/BrandDetailView.js +16 -200
- data/solara/lib/core/dashboard/brand/SectionsFormManager.js +232 -0
- data/solara/lib/core/dashboard/brand/brand.html +187 -177
- data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +2 -5
- data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +36 -133
- data/solara/lib/core/dashboard/brands/Brands.js +31 -0
- data/solara/lib/core/dashboard/brands/BrandsController.js +0 -5
- data/solara/lib/core/dashboard/brands/BrandsView.js +2 -2
- data/solara/lib/core/dashboard/brands/brands.html +71 -52
- data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +6 -6
- data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +4 -4
- data/solara/lib/core/dashboard/component/ConfirmationDialog.js +15 -10
- data/solara/lib/core/dashboard/component/EditJsonSheet.js +160 -0
- data/solara/lib/core/dashboard/component/MessageBottomSheet.js +5 -5
- data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +5 -3
- data/solara/lib/core/dashboard/handler/base_handler.rb +1 -0
- data/solara/lib/core/dashboard/handler/edit_section_handler.rb +1 -5
- data/solara/lib/core/doctor/schema/brand_configurations.json +0 -8
- data/solara/lib/core/doctor/schema/platform/global/resources_manifest.json +30 -0
- data/solara/lib/core/doctor/schema/platform/json_manifest.json +57 -0
- data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +35 -1
- data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +30 -1
- data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +35 -1
- data/solara/lib/core/doctor/validator/template/template_validator.rb +9 -9
- data/solara/lib/core/scripts/brand_config_manager.rb +1 -1
- data/solara/lib/core/scripts/brand_configurations_manager.rb +41 -0
- data/solara/lib/core/scripts/code_generator.rb +342 -118
- data/solara/lib/core/scripts/file_path.rb +21 -1
- data/solara/lib/core/scripts/gitignore_manager.rb +11 -3
- data/solara/lib/core/scripts/json_manifest_processor.rb +95 -0
- data/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb +11 -1
- data/solara/lib/core/scripts/resource_manifest_processor.rb +151 -0
- data/solara/lib/core/scripts/solara_status_manager.rb +1 -1
- data/solara/lib/core/scripts/theme_generator.rb +21 -242
- data/solara/lib/core/solara_configurator.rb +1 -1
- data/solara/lib/core/template/brands/global/resources_manifest.json +10 -0
- data/solara/lib/core/template/brands/json/Json-Manifest.md +61 -0
- data/solara/lib/core/template/brands/json/json_manifest.json +18 -0
- data/solara/lib/core/template/brands/shared/theme.json +213 -29
- data/solara/lib/core/template/config/android_template_config.json +50 -0
- data/solara/lib/core/template/config/flutter_template_config.json +35 -0
- data/solara/lib/core/template/config/ios_template_config.json +50 -0
- data/solara/lib/core/template/configurations.json +46 -0
- data/solara/lib/core/template/project_template_generator.rb +2 -0
- data/solara/lib/solara/version.rb +1 -1
- data/solara/lib/solara.rb +19 -0
- metadata +13 -4
- data/solara/lib/core/dashboard/component/AddFieldSheet.js +0 -175
- data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +0 -73
@@ -16,19 +16,48 @@ module Language
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
class ClassNameRegistry
|
20
|
+
def initialize(custom_types)
|
21
|
+
@custom_types = custom_types
|
22
|
+
@registry = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def register(original_name, prefixed_name, context = [])
|
26
|
+
full_context = (context + [original_name]).join('.')
|
27
|
+
@registry[full_context] = prefixed_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(name, context = [])
|
31
|
+
full_context = (context + [name]).join('.')
|
32
|
+
result = @registry[full_context] || name
|
33
|
+
@custom_types[result] || result
|
34
|
+
end
|
35
|
+
|
36
|
+
def original_names
|
37
|
+
@registry.keys
|
38
|
+
end
|
39
|
+
|
40
|
+
def prefixed_names
|
41
|
+
@registry.values
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
19
45
|
class CodeGenerator
|
20
46
|
def initialize(
|
21
47
|
json:,
|
22
48
|
language:,
|
23
|
-
parent_class_name
|
49
|
+
parent_class_name:,
|
50
|
+
custom_types: {}
|
51
|
+
)
|
24
52
|
@json = json
|
25
53
|
@language = language
|
26
54
|
@parent_class_name = parent_class_name
|
55
|
+
@custom_types = custom_types
|
27
56
|
@generator = create_language_generator
|
28
57
|
end
|
29
58
|
|
30
59
|
def generate
|
31
|
-
|
60
|
+
@generator.generate_content
|
32
61
|
end
|
33
62
|
|
34
63
|
private
|
@@ -36,11 +65,11 @@ class CodeGenerator
|
|
36
65
|
def create_language_generator
|
37
66
|
case @language
|
38
67
|
when Language::Kotlin
|
39
|
-
KotlinCodeGenerator.new(@json, @parent_class_name)
|
68
|
+
KotlinCodeGenerator.new(@json, @parent_class_name, @custom_types)
|
40
69
|
when Language::Swift
|
41
|
-
SwiftCodeGenerator.new(@json, @parent_class_name)
|
70
|
+
SwiftCodeGenerator.new(@json, @parent_class_name, @custom_types)
|
42
71
|
when Language::Dart
|
43
|
-
DartCodeGenerator.new(@json, @parent_class_name)
|
72
|
+
DartCodeGenerator.new(@json, @parent_class_name, @custom_types)
|
44
73
|
else
|
45
74
|
raise ArgumentError, "Unsupported language: #{@language}"
|
46
75
|
end
|
@@ -48,9 +77,12 @@ class CodeGenerator
|
|
48
77
|
end
|
49
78
|
|
50
79
|
class BaseCodeGenerator
|
51
|
-
def initialize(json, parent_class_name)
|
80
|
+
def initialize(json, parent_class_name, custom_types)
|
52
81
|
@parent_class_name = parent_class_name
|
53
82
|
@json = json
|
83
|
+
@custom_types = custom_types
|
84
|
+
@class_registry = ClassNameRegistry.new(custom_types)
|
85
|
+
register_class_names(@parent_class_name, @json)
|
54
86
|
end
|
55
87
|
|
56
88
|
def generate_content
|
@@ -65,35 +97,60 @@ class BaseCodeGenerator
|
|
65
97
|
content += "\n"
|
66
98
|
end
|
67
99
|
|
68
|
-
|
100
|
+
other_classes = language_specific_classes
|
101
|
+
other_classes.each do |item|
|
102
|
+
content += item
|
103
|
+
end
|
104
|
+
|
105
|
+
content + "\n"
|
69
106
|
end
|
70
107
|
|
71
|
-
|
72
|
-
|
108
|
+
private
|
109
|
+
|
110
|
+
def register_class_names(class_name, json, context = [])
|
111
|
+
full_context = context + [class_name]
|
112
|
+
prefixed_name = full_context.join
|
113
|
+
@class_registry.register(class_name, prefixed_name, context)
|
114
|
+
|
115
|
+
json.each do |key, value|
|
116
|
+
if value.is_a?(Hash)
|
117
|
+
nested_class_name = capitalize(key)
|
118
|
+
register_class_names(nested_class_name, value, full_context)
|
119
|
+
elsif value.is_a?(Array) && value.any? { |item| item.is_a?(Hash) }
|
120
|
+
nested_class_name = "#{capitalize(key)}Item"
|
121
|
+
register_class_names(nested_class_name, value.first, full_context)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def generate_classes(class_name, json, classes, context = [], generate_json: false)
|
127
|
+
full_context = context + [class_name]
|
128
|
+
prefixed_class_name = @class_registry.get(class_name, context)
|
129
|
+
content = class_declaration(prefixed_class_name)
|
73
130
|
constructor_params = []
|
74
131
|
|
75
132
|
json.each do |key, value|
|
76
|
-
type = value_type(value, key)
|
133
|
+
type = value_type(value, key, full_context)
|
77
134
|
content += property_declaration(key, type, json)
|
78
135
|
constructor_params << constructor_parameter(key, type)
|
79
136
|
end
|
80
137
|
|
81
138
|
content += property_declaration("asJson", "String", json)
|
82
139
|
|
83
|
-
content += constructor_declaration(
|
84
|
-
content += instance_declaration(
|
85
|
-
content += json_methods(json,
|
140
|
+
content += constructor_declaration(prefixed_class_name, constructor_params)
|
141
|
+
content += instance_declaration(prefixed_class_name, json, full_context)
|
142
|
+
content += json_methods(json, prefixed_class_name, full_context)
|
86
143
|
content += class_closing
|
87
144
|
|
88
145
|
classes << content
|
89
146
|
|
90
147
|
json.each do |key, value|
|
91
148
|
if value.is_a?(Hash)
|
92
|
-
nested_class_name =
|
93
|
-
generate_classes(nested_class_name, value, classes)
|
149
|
+
nested_class_name = capitalize(key)
|
150
|
+
generate_classes(nested_class_name, value, classes, full_context)
|
94
151
|
elsif value.is_a?(Array) && value.any? { |item| item.is_a?(Hash) }
|
95
|
-
|
96
|
-
generate_classes(nested_class_name, value.first, classes)
|
152
|
+
nested_class_name = "#{capitalize(key)}Item"
|
153
|
+
generate_classes(nested_class_name, value.first, classes, full_context)
|
97
154
|
end
|
98
155
|
end
|
99
156
|
end
|
@@ -106,6 +163,10 @@ class BaseCodeGenerator
|
|
106
163
|
raise NotImplementedError, "Subclasses must implement property_declaration"
|
107
164
|
end
|
108
165
|
|
166
|
+
def color_type
|
167
|
+
raise NotImplementedError, "Subclasses must implement property_declaration"
|
168
|
+
end
|
169
|
+
|
109
170
|
def constructor_parameter(key, type)
|
110
171
|
raise NotImplementedError, "Subclasses must implement constructor_parameter"
|
111
172
|
end
|
@@ -114,11 +175,11 @@ class BaseCodeGenerator
|
|
114
175
|
raise NotImplementedError, "Subclasses must implement constructor_declaration"
|
115
176
|
end
|
116
177
|
|
117
|
-
def instance_declaration(class_name, json)
|
178
|
+
def instance_declaration(class_name, json, context)
|
118
179
|
raise NotImplementedError, "Subclasses must implement instance_declaration"
|
119
180
|
end
|
120
181
|
|
121
|
-
def json_methods(json, class_name)
|
182
|
+
def json_methods(json, class_name, context)
|
122
183
|
raise NotImplementedError, "Subclasses must implement json_methods"
|
123
184
|
end
|
124
185
|
|
@@ -130,12 +191,12 @@ class BaseCodeGenerator
|
|
130
191
|
raise NotImplementedError, "Subclasses must implement language_specific_imports"
|
131
192
|
end
|
132
193
|
|
133
|
-
def value_type(value, class_prefix)
|
194
|
+
def value_type(value, class_prefix, context = [])
|
134
195
|
raise NotImplementedError, "Subclasses must implement value_type"
|
135
196
|
end
|
136
197
|
|
137
|
-
def value_for(value, class_prefix, indent)
|
138
|
-
raise NotImplementedError, "Subclasses must implement
|
198
|
+
def value_for(value, class_prefix, indent, context = [])
|
199
|
+
raise NotImplementedError, "Subclasses must implement value_for"
|
139
200
|
end
|
140
201
|
|
141
202
|
def color_for(value)
|
@@ -145,6 +206,14 @@ class BaseCodeGenerator
|
|
145
206
|
def language_specific_null
|
146
207
|
raise NotImplementedError, "Subclasses must implement language_specific_null"
|
147
208
|
end
|
209
|
+
|
210
|
+
def language_specific_classes
|
211
|
+
[]
|
212
|
+
end
|
213
|
+
|
214
|
+
def capitalize(string)
|
215
|
+
"#{string[0].upcase}#{string[1..-1]}"
|
216
|
+
end
|
148
217
|
end
|
149
218
|
|
150
219
|
class KotlinCodeGenerator < BaseCodeGenerator
|
@@ -161,11 +230,17 @@ class KotlinCodeGenerator < BaseCodeGenerator
|
|
161
230
|
if key == "asJson"
|
162
231
|
json_string = json.to_json.gsub('"', '\\"')
|
163
232
|
" val #{key}: String = \"#{json_string}\",\n"
|
233
|
+
elsif json[key].is_a?(String) && ColorDetector.new(json[key]).color?
|
234
|
+
" val #{key}: #{color_type},\n"
|
164
235
|
else
|
165
236
|
" val #{key}: #{type},\n"
|
166
237
|
end
|
167
238
|
end
|
168
239
|
|
240
|
+
def color_type
|
241
|
+
"Int"
|
242
|
+
end
|
243
|
+
|
169
244
|
def constructor_parameter(key, type)
|
170
245
|
"val #{key}: #{type}"
|
171
246
|
end
|
@@ -174,11 +249,11 @@ class KotlinCodeGenerator < BaseCodeGenerator
|
|
174
249
|
"): Serializable {\n"
|
175
250
|
end
|
176
251
|
|
177
|
-
def instance_declaration(class_name, json)
|
178
|
-
" companion object {\n val instance = #{class_name}(\n#{json.map { |k, v| " #{k} = #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n }\n"
|
252
|
+
def instance_declaration(class_name, json, context)
|
253
|
+
" companion object {\n val instance = #{class_name}(\n#{json.map { |k, v| " #{k} = #{value_for(v, k, ' ', context)}" }.join(",\n")}\n )\n }\n"
|
179
254
|
end
|
180
255
|
|
181
|
-
def json_methods(json, class_name)
|
256
|
+
def json_methods(json, class_name, context)
|
182
257
|
""
|
183
258
|
end
|
184
259
|
|
@@ -186,41 +261,47 @@ class KotlinCodeGenerator < BaseCodeGenerator
|
|
186
261
|
"}\n"
|
187
262
|
end
|
188
263
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
else
|
195
|
-
"\"#{value}\"" # Use double quotes for Kotlin strings
|
196
|
-
end
|
197
|
-
when Integer, Float
|
198
|
-
value.to_s
|
199
|
-
when TrueClass, FalseClass
|
200
|
-
value.to_s
|
201
|
-
when Array
|
202
|
-
if value.empty?
|
203
|
-
"emptyList()" # Use Kotlin's emptyList() for empty arrays
|
204
|
-
elsif value.all? { |item| item.is_a?(Hash) }
|
205
|
-
array_items = value.map do |item|
|
206
|
-
item_values = item.map { |k, v| "#{k} = #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
|
207
|
-
"#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item(\n#{indent} #{item_values}\n#{indent} )"
|
208
|
-
end.join(",\n#{indent} ")
|
209
|
-
"listOf(\n#{indent} #{array_items}\n#{indent})"
|
210
|
-
else
|
211
|
-
array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ")
|
212
|
-
"listOf(\n#{indent} #{array_items}\n#{indent})" # Use listOf for non-empty lists
|
213
|
-
end
|
214
|
-
when Hash
|
215
|
-
"#{class_prefix[0].upcase}#{class_prefix[1..-1]}.instance"
|
264
|
+
def value_for(value, class_prefix, indent, context = [])
|
265
|
+
case value
|
266
|
+
when String
|
267
|
+
if ColorDetector.new(value).color?
|
268
|
+
color_for(value)
|
216
269
|
else
|
217
|
-
|
270
|
+
"\"#{value}\"" # Use double quotes for Kotlin strings
|
218
271
|
end
|
272
|
+
when Integer
|
273
|
+
value.to_s
|
274
|
+
when Float
|
275
|
+
"#{value}F" # Add 'F' suffix for float values
|
276
|
+
when TrueClass, FalseClass
|
277
|
+
value.to_s
|
278
|
+
when Array
|
279
|
+
if value.empty?
|
280
|
+
"emptyList()" # Use Kotlin's emptyList() for empty arrays
|
281
|
+
elsif value.all? { |item| item.is_a?(Hash) }
|
282
|
+
array_items = value.map do |item|
|
283
|
+
item_values = item.map { |k, v| "#{k} = #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
|
284
|
+
"#{@class_registry.get("#{capitalize(class_prefix)}Item")}(\n#{indent} #{item_values}\n#{indent} )"
|
285
|
+
end.join(",\n#{indent} ")
|
286
|
+
"listOf(\n#{indent} #{array_items}\n#{indent})"
|
287
|
+
else
|
288
|
+
array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ")
|
289
|
+
"listOf(\n#{indent} #{array_items}\n#{indent})" # Use listOf for non-empty lists
|
290
|
+
end
|
291
|
+
when Hash
|
292
|
+
"#{@class_registry.get(capitalize(class_prefix), context)}.instance"
|
293
|
+
else
|
294
|
+
language_specific_null
|
219
295
|
end
|
296
|
+
end
|
220
297
|
|
221
|
-
def value_type(value, class_prefix)
|
298
|
+
def value_type(value, class_prefix, context = [])
|
222
299
|
case value
|
223
|
-
when String
|
300
|
+
when String
|
301
|
+
if ColorDetector.new(value).color?
|
302
|
+
return color_type
|
303
|
+
end
|
304
|
+
return 'String'
|
224
305
|
when Integer then 'Int'
|
225
306
|
when Float then 'Float'
|
226
307
|
when TrueClass, FalseClass then 'Boolean'
|
@@ -236,11 +317,11 @@ class KotlinCodeGenerator < BaseCodeGenerator
|
|
236
317
|
elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
|
237
318
|
'List<Boolean>'
|
238
319
|
elsif value.all? { |item| item.is_a?(Hash) }
|
239
|
-
|
320
|
+
"List<#{@class_registry.get("#{capitalize(class_prefix)}Item")}>"
|
240
321
|
else
|
241
322
|
'List<Any>'
|
242
323
|
end
|
243
|
-
when Hash then
|
324
|
+
when Hash then @class_registry.get(capitalize(class_prefix), context)
|
244
325
|
else 'Any'
|
245
326
|
end
|
246
327
|
end
|
@@ -250,66 +331,109 @@ class KotlinCodeGenerator < BaseCodeGenerator
|
|
250
331
|
end
|
251
332
|
|
252
333
|
def language_specific_null
|
253
|
-
|
334
|
+
"null"
|
254
335
|
end
|
255
336
|
end
|
256
337
|
|
257
338
|
class SwiftCodeGenerator < BaseCodeGenerator
|
258
339
|
def language_specific_imports
|
340
|
+
"import Foundation\n" +
|
259
341
|
"import UIKit\n\n"
|
260
342
|
end
|
261
343
|
|
262
344
|
def class_declaration(class_name)
|
263
|
-
"
|
345
|
+
"\nclass #{class_name}: Codable {\n"
|
264
346
|
end
|
265
347
|
|
266
348
|
def property_declaration(key, type, json)
|
267
349
|
if key == "asJson"
|
268
350
|
json_string = json.to_json.gsub('"', '\\"')
|
269
351
|
" let #{key}: String = \"#{json_string}\"\n"
|
352
|
+
elsif json[key].is_a?(String) && ColorDetector.new(json[key]).color?
|
353
|
+
" let #{key}: #{color_type}\n"
|
270
354
|
else
|
271
355
|
" let #{key}: #{type}\n"
|
272
356
|
end
|
273
357
|
end
|
274
358
|
|
359
|
+
def color_type
|
360
|
+
"UIColor"
|
361
|
+
end
|
362
|
+
|
275
363
|
def constructor_parameter(key, type)
|
276
364
|
"#{key}: #{type}"
|
277
365
|
end
|
278
366
|
|
279
367
|
def constructor_declaration(class_name, params)
|
280
|
-
"
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
368
|
+
" init(#{params.join(", ")}) {\n" +
|
369
|
+
params.map { |param| " self.#{param.split(":").first} = #{param.split(":").first}" }.join("\n") +
|
370
|
+
"\n }\n\n"
|
371
|
+
end
|
372
|
+
|
373
|
+
def instance_declaration(class_name, json, context)
|
374
|
+
" static let shared = #{class_name}(\n" +
|
375
|
+
json.map { |k, v| " #{k}: #{value_for(v, k, ' ', context)}" }.join(",\n") +
|
376
|
+
"\n )\n\n"
|
377
|
+
end
|
378
|
+
|
379
|
+
def json_methods(json, class_name, context)
|
380
|
+
prefixed_class_name = @class_registry.get(capitalize(class_name))
|
381
|
+
coding_keys = json.map { |key, _| " case #{key}" }.join("\n")
|
382
|
+
|
383
|
+
<<-SWIFT
|
384
|
+
private enum CodingKeys: String, CodingKey {
|
385
|
+
#{coding_keys}
|
386
|
+
}
|
387
|
+
|
388
|
+
func toJson() -> String? {
|
389
|
+
let encoder = JSONEncoder()
|
390
|
+
if let jsonData = try? encoder.encode(self) {
|
391
|
+
return String(data: jsonData, encoding: .utf8)
|
392
|
+
}
|
393
|
+
return nil
|
394
|
+
}
|
395
|
+
|
396
|
+
required init(from decoder: Decoder) throws {
|
397
|
+
let container = try decoder.container(keyedBy: CodingKeys.self)
|
398
|
+
#{json.map { |key, value|
|
399
|
+
if value.is_a?(String) && ColorDetector.new(value).color?
|
400
|
+
"self.#{key} = UIColor(hex: try container.decode(String.self, forKey: .#{key}))"
|
401
|
+
else
|
402
|
+
"self.#{key} = try container.decode(#{value_type(value, key, context)}.self, forKey: .#{key})"
|
403
|
+
end
|
404
|
+
}.join("\n ")}
|
405
|
+
}
|
406
|
+
|
407
|
+
func encode(to encoder: Encoder) throws {
|
408
|
+
var container = encoder.container(keyedBy: CodingKeys.self)
|
409
|
+
#{json.map { |key, value|
|
410
|
+
if value.is_a?(String) && ColorDetector.new(value).color?
|
411
|
+
"try container.encode(#{key}.toHexString(), forKey: .#{key})"
|
412
|
+
else
|
413
|
+
"try container.encode(#{key}, forKey: .#{key})"
|
414
|
+
end
|
415
|
+
}.join("\n ")}
|
416
|
+
}
|
286
417
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
" let decoder = JSONDecoder()\n" +
|
297
|
-
" if let jsonData = json.data(using: .utf8),\n" +
|
298
|
-
" let result = try? decoder.decode(#{class_name}.self, from: jsonData) {\n" +
|
299
|
-
" return result\n" +
|
300
|
-
" }\n" +
|
301
|
-
" return nil\n" +
|
302
|
-
" }\n"
|
418
|
+
static func fromJson(_ json: String) -> #{prefixed_class_name}? {
|
419
|
+
let decoder = JSONDecoder()
|
420
|
+
if let jsonData = json.data(using: .utf8),
|
421
|
+
let result = try? decoder.decode(#{prefixed_class_name}.self, from: jsonData) {
|
422
|
+
return result
|
423
|
+
}
|
424
|
+
return nil
|
425
|
+
}
|
426
|
+
SWIFT
|
303
427
|
end
|
304
428
|
|
305
429
|
def class_closing
|
306
430
|
"}\n"
|
307
431
|
end
|
308
432
|
|
309
|
-
def value_for(value, class_prefix, indent)
|
433
|
+
def value_for(value, class_prefix, indent, context = [])
|
310
434
|
case value
|
311
435
|
when String
|
312
|
-
if
|
436
|
+
if ColorDetector.new(value).color?
|
313
437
|
color_for(value)
|
314
438
|
else
|
315
439
|
"\"#{value}\""
|
@@ -324,7 +448,7 @@ class SwiftCodeGenerator < BaseCodeGenerator
|
|
324
448
|
elsif value.all? { |item| item.is_a?(Hash) }
|
325
449
|
array_items = value.map do |item|
|
326
450
|
item_values = item.map { |k, v| "#{k}: #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
|
327
|
-
"#{
|
451
|
+
"#{@class_registry.get("#{capitalize(class_prefix)}Item")}(\n#{indent} #{item_values}\n#{indent} )"
|
328
452
|
end.join(",\n#{indent} ")
|
329
453
|
"[\n#{indent} #{array_items}\n#{indent}]"
|
330
454
|
else
|
@@ -332,15 +456,19 @@ class SwiftCodeGenerator < BaseCodeGenerator
|
|
332
456
|
"[\n#{indent} #{array_items}\n#{indent}]"
|
333
457
|
end
|
334
458
|
when Hash
|
335
|
-
"#{
|
459
|
+
"#{@class_registry.get(capitalize(class_prefix), context)}.shared"
|
336
460
|
else
|
337
461
|
language_specific_null
|
338
462
|
end
|
339
463
|
end
|
340
464
|
|
341
|
-
def value_type(value, class_prefix)
|
465
|
+
def value_type(value, class_prefix, context = [])
|
342
466
|
case value
|
343
|
-
when String
|
467
|
+
when String
|
468
|
+
if ColorDetector.new(value).color?
|
469
|
+
return color_type
|
470
|
+
end
|
471
|
+
return 'String'
|
344
472
|
when Integer then 'Int'
|
345
473
|
when Float then 'Double'
|
346
474
|
when TrueClass, FalseClass then 'Bool'
|
@@ -356,76 +484,157 @@ class SwiftCodeGenerator < BaseCodeGenerator
|
|
356
484
|
elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
|
357
485
|
'[Bool]'
|
358
486
|
elsif value.all? { |item| item.is_a?(Hash) }
|
359
|
-
"[#{
|
487
|
+
"[#{@class_registry.get("#{capitalize(class_prefix)}Item")}]"
|
360
488
|
else
|
361
489
|
'[Any]'
|
362
490
|
end
|
363
|
-
when Hash then
|
491
|
+
when Hash then @class_registry.get(capitalize(class_prefix), context)
|
364
492
|
else 'Any'
|
365
493
|
end
|
366
494
|
end
|
367
495
|
|
368
496
|
def color_for(value)
|
369
|
-
|
370
|
-
"UIColor(red: #{r}/255.0, green: #{g}/255.0, blue: #{b}/255.0, alpha: 1.0)"
|
497
|
+
"UIColor(hex: \"#{value}\")"
|
371
498
|
end
|
372
499
|
|
373
500
|
def language_specific_null
|
374
|
-
|
501
|
+
"nil"
|
502
|
+
end
|
503
|
+
|
504
|
+
def language_specific_classes
|
505
|
+
[
|
506
|
+
<<-SWIFT
|
507
|
+
private extension UIColor {
|
508
|
+
convenience init(hex: String) {
|
509
|
+
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
510
|
+
var int = UInt64()
|
511
|
+
Scanner(string: hex).scanHexInt64(&int)
|
512
|
+
let a, r, g, b: UInt64
|
513
|
+
switch hex.count {
|
514
|
+
case 3: // RGB (12-bit)
|
515
|
+
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
|
516
|
+
case 6: // RGB (24-bit)
|
517
|
+
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
|
518
|
+
case 8: // ARGB (32-bit)
|
519
|
+
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
|
520
|
+
default:
|
521
|
+
(a, r, g, b) = (255, 0, 0, 0)
|
522
|
+
}
|
523
|
+
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
|
524
|
+
}
|
525
|
+
|
526
|
+
func toHexString() -> String {
|
527
|
+
var r:CGFloat = 0
|
528
|
+
var g:CGFloat = 0
|
529
|
+
var b:CGFloat = 0
|
530
|
+
var a:CGFloat = 0
|
531
|
+
|
532
|
+
getRed(&r, green: &g, blue: &b, alpha: &a)
|
533
|
+
|
534
|
+
let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0
|
535
|
+
|
536
|
+
return String(format: "#%06x", rgb)
|
537
|
+
}
|
538
|
+
}
|
539
|
+
SWIFT
|
540
|
+
]
|
375
541
|
end
|
376
542
|
end
|
377
543
|
|
378
544
|
class DartCodeGenerator < BaseCodeGenerator
|
379
545
|
def language_specific_imports
|
380
|
-
"import '
|
381
|
-
"import 'dart
|
546
|
+
"import 'dart:convert';\n" +
|
547
|
+
"import 'package:flutter/material.dart';\n\n"
|
382
548
|
end
|
383
549
|
|
384
550
|
def class_declaration(class_name)
|
385
|
-
"
|
551
|
+
"\nclass #{class_name} {\n"
|
386
552
|
end
|
387
553
|
|
388
554
|
def property_declaration(key, type, json)
|
389
555
|
if key == "asJson"
|
390
556
|
json_string = json.to_json.gsub('"', '\\"')
|
391
557
|
" final String #{key} = \"#{json_string}\";\n"
|
558
|
+
elsif json[key].is_a?(String) && ColorDetector.new(json[key]).color?
|
559
|
+
" final #{color_type} #{key};\n"
|
392
560
|
else
|
393
561
|
" final #{type} #{key};\n"
|
394
562
|
end
|
395
563
|
end
|
396
564
|
|
565
|
+
def color_type
|
566
|
+
"Color"
|
567
|
+
end
|
568
|
+
|
397
569
|
def constructor_parameter(key, type)
|
398
|
-
"required this.#{key}"
|
570
|
+
"\n required this.#{key}"
|
399
571
|
end
|
400
572
|
|
401
573
|
def constructor_declaration(class_name, params)
|
402
|
-
|
574
|
+
" const #{class_name}({#{params.join(", ")}});\n\n"
|
403
575
|
end
|
404
576
|
|
405
|
-
def instance_declaration(class_name, json)
|
406
|
-
" static const #{class_name} instance = #{class_name}(\n
|
577
|
+
def instance_declaration(class_name, json, context)
|
578
|
+
" static const #{class_name} instance = #{class_name}(\n" +
|
579
|
+
json.map { |k, v| " #{k}: #{value_for(v, k, ' ', context)}" }.join(",\n") +
|
580
|
+
"\n );\n\n"
|
407
581
|
end
|
408
582
|
|
409
|
-
def json_methods(json, class_name)
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
583
|
+
def json_methods(json, class_name, context)
|
584
|
+
prefixed_class_name = @class_registry.get(class_name)
|
585
|
+
from_json_content = json.map do |key, value|
|
586
|
+
if value.is_a?(String) && ColorDetector.new(value).color?
|
587
|
+
" #{key}: Color(int.parse(json['#{key}'].substring(1, 7), radix: 16) + 0xFF000000)"
|
588
|
+
else
|
589
|
+
" #{key}: json['#{key}']"
|
590
|
+
end
|
591
|
+
end.join(",\n")
|
592
|
+
|
593
|
+
to_json_content = json.map do |key, value|
|
594
|
+
if value.is_a?(String) && ColorDetector.new(value).color?
|
595
|
+
"'#{key}': '#${#{key}.value.toRadixString(16).padLeft(8, '0').substring(2)}'"
|
596
|
+
else
|
597
|
+
"'#{key}': #{key}"
|
598
|
+
end
|
599
|
+
end.join(",\n ")
|
600
|
+
|
601
|
+
<<-DART
|
602
|
+
factory #{prefixed_class_name}.fromJson(Map<String, dynamic> json) {
|
603
|
+
return #{prefixed_class_name}(
|
604
|
+
#{from_json_content}
|
605
|
+
);
|
606
|
+
}
|
607
|
+
|
608
|
+
Map<String, dynamic> toJson() {
|
609
|
+
return {
|
610
|
+
#{to_json_content}
|
611
|
+
};
|
612
|
+
}
|
613
|
+
|
614
|
+
String toJsonString() {
|
615
|
+
return json.encode(toJson());
|
616
|
+
}
|
617
|
+
|
618
|
+
static #{prefixed_class_name}? fromJsonString(String jsonString) {
|
619
|
+
try {
|
620
|
+
final Map<String, dynamic> jsonMap = json.decode(jsonString);
|
621
|
+
return #{prefixed_class_name}.fromJson(jsonMap);
|
622
|
+
} catch (e) {
|
623
|
+
print('Error parsing JSON: $e');
|
624
|
+
return null;
|
625
|
+
}
|
626
|
+
}
|
627
|
+
DART
|
419
628
|
end
|
420
629
|
|
421
630
|
def class_closing
|
422
631
|
"}\n"
|
423
632
|
end
|
424
633
|
|
425
|
-
def value_for(value, class_prefix, indent)
|
634
|
+
def value_for(value, class_prefix, indent, context = [])
|
426
635
|
case value
|
427
636
|
when String
|
428
|
-
if
|
637
|
+
if ColorDetector.new(value).color?
|
429
638
|
color_for(value)
|
430
639
|
else
|
431
640
|
"\"#{value}\""
|
@@ -440,7 +649,7 @@ class DartCodeGenerator < BaseCodeGenerator
|
|
440
649
|
elsif value.all? { |item| item.is_a?(Hash) }
|
441
650
|
array_items = value.map do |item|
|
442
651
|
item_values = item.map { |k, v| "#{k}: #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
|
443
|
-
"#{
|
652
|
+
"#{@class_registry.get("#{capitalize(class_prefix)}Item")}(\n#{indent} #{item_values}\n#{indent} )"
|
444
653
|
end.join(",\n#{indent} ")
|
445
654
|
"[\n#{indent} #{array_items}\n#{indent}]"
|
446
655
|
else
|
@@ -448,15 +657,19 @@ class DartCodeGenerator < BaseCodeGenerator
|
|
448
657
|
"[\n#{indent} #{array_items}\n#{indent}]"
|
449
658
|
end
|
450
659
|
when Hash
|
451
|
-
"#{
|
660
|
+
"#{@class_registry.get(capitalize(class_prefix), context)}.instance"
|
452
661
|
else
|
453
662
|
language_specific_null
|
454
663
|
end
|
455
664
|
end
|
456
665
|
|
457
|
-
def value_type(value, class_prefix)
|
666
|
+
def value_type(value, class_prefix, context = [])
|
458
667
|
case value
|
459
|
-
when String
|
668
|
+
when String
|
669
|
+
if ColorDetector.new(value).color?
|
670
|
+
return color_type
|
671
|
+
end
|
672
|
+
return 'String'
|
460
673
|
when Integer then 'int'
|
461
674
|
when Float then 'double'
|
462
675
|
when TrueClass, FalseClass then 'bool'
|
@@ -472,11 +685,11 @@ class DartCodeGenerator < BaseCodeGenerator
|
|
472
685
|
elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
|
473
686
|
'List<bool>'
|
474
687
|
elsif value.all? { |item| item.is_a?(Hash) }
|
475
|
-
"List<#{
|
688
|
+
"List<#{@class_registry.get("#{capitalize(class_prefix)}Item")}>"
|
476
689
|
else
|
477
690
|
'List<dynamic>'
|
478
691
|
end
|
479
|
-
when Hash then
|
692
|
+
when Hash then @class_registry.get(capitalize(class_prefix), context)
|
480
693
|
else 'dynamic'
|
481
694
|
end
|
482
695
|
end
|
@@ -486,6 +699,17 @@ class DartCodeGenerator < BaseCodeGenerator
|
|
486
699
|
end
|
487
700
|
|
488
701
|
def language_specific_null
|
489
|
-
|
702
|
+
"null"
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
class ColorDetector
|
707
|
+
def initialize(value)
|
708
|
+
@value = value
|
709
|
+
end
|
710
|
+
|
711
|
+
def color?
|
712
|
+
# Check for 6-character (RGB) or 8-character (RGBA) hex color formats
|
713
|
+
@value.is_a?(String) && @value.match?(/^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/)
|
490
714
|
end
|
491
715
|
end
|