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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/solara/lib/.DS_Store +0 -0
  3. data/solara/lib/core/.DS_Store +0 -0
  4. data/solara/lib/core/brands/brand_switcher.rb +58 -1
  5. data/solara/lib/core/dashboard/brand/BrandDetail.js +34 -2
  6. data/solara/lib/core/dashboard/brand/BrandDetailController.js +23 -233
  7. data/solara/lib/core/dashboard/brand/BrandDetailModel.js +13 -5
  8. data/solara/lib/core/dashboard/brand/BrandDetailView.js +16 -200
  9. data/solara/lib/core/dashboard/brand/SectionsFormManager.js +232 -0
  10. data/solara/lib/core/dashboard/brand/brand.html +187 -177
  11. data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +2 -5
  12. data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +36 -133
  13. data/solara/lib/core/dashboard/brands/Brands.js +31 -0
  14. data/solara/lib/core/dashboard/brands/BrandsController.js +0 -5
  15. data/solara/lib/core/dashboard/brands/BrandsView.js +2 -2
  16. data/solara/lib/core/dashboard/brands/brands.html +71 -52
  17. data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +6 -6
  18. data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +4 -4
  19. data/solara/lib/core/dashboard/component/ConfirmationDialog.js +15 -10
  20. data/solara/lib/core/dashboard/component/EditJsonSheet.js +160 -0
  21. data/solara/lib/core/dashboard/component/MessageBottomSheet.js +5 -5
  22. data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +5 -3
  23. data/solara/lib/core/dashboard/handler/base_handler.rb +1 -0
  24. data/solara/lib/core/dashboard/handler/edit_section_handler.rb +1 -5
  25. data/solara/lib/core/doctor/schema/brand_configurations.json +0 -8
  26. data/solara/lib/core/doctor/schema/platform/global/resources_manifest.json +30 -0
  27. data/solara/lib/core/doctor/schema/platform/json_manifest.json +57 -0
  28. data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +35 -1
  29. data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +30 -1
  30. data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +35 -1
  31. data/solara/lib/core/doctor/validator/template/template_validator.rb +9 -9
  32. data/solara/lib/core/scripts/brand_config_manager.rb +1 -1
  33. data/solara/lib/core/scripts/brand_configurations_manager.rb +41 -0
  34. data/solara/lib/core/scripts/code_generator.rb +342 -118
  35. data/solara/lib/core/scripts/file_path.rb +21 -1
  36. data/solara/lib/core/scripts/gitignore_manager.rb +11 -3
  37. data/solara/lib/core/scripts/json_manifest_processor.rb +95 -0
  38. data/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb +11 -1
  39. data/solara/lib/core/scripts/resource_manifest_processor.rb +151 -0
  40. data/solara/lib/core/scripts/solara_status_manager.rb +1 -1
  41. data/solara/lib/core/scripts/theme_generator.rb +21 -242
  42. data/solara/lib/core/solara_configurator.rb +1 -1
  43. data/solara/lib/core/template/brands/global/resources_manifest.json +10 -0
  44. data/solara/lib/core/template/brands/json/Json-Manifest.md +61 -0
  45. data/solara/lib/core/template/brands/json/json_manifest.json +18 -0
  46. data/solara/lib/core/template/brands/shared/theme.json +213 -29
  47. data/solara/lib/core/template/config/android_template_config.json +50 -0
  48. data/solara/lib/core/template/config/flutter_template_config.json +35 -0
  49. data/solara/lib/core/template/config/ios_template_config.json +50 -0
  50. data/solara/lib/core/template/configurations.json +46 -0
  51. data/solara/lib/core/template/project_template_generator.rb +2 -0
  52. data/solara/lib/solara/version.rb +1 -1
  53. data/solara/lib/solara.rb +19 -0
  54. metadata +13 -4
  55. data/solara/lib/core/dashboard/component/AddFieldSheet.js +0 -175
  56. 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
- @generator.generate_content
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
- content
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
- def generate_classes(class_name, json, classes, generate_json: false)
72
- content = class_declaration(class_name)
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(class_name, constructor_params)
84
- content += instance_declaration(class_name, json)
85
- content += json_methods(json, class_name)
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 = "#{key[0].upcase}#{key[1..-1]}" # Capitalize first character
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
- nested_class_name = "#{key[0].upcase}#{key[1..-1]}Item" # Capitalize first character
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 value_type"
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
- def value_for(value, class_prefix, indent)
190
- case value
191
- when String
192
- if value.start_with?('#') && value.length == 7 # Assume it's a color
193
- color_for(value)
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
- language_specific_null
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 then '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
- "List<#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item>"
320
+ "List<#{@class_registry.get("#{capitalize(class_prefix)}Item")}>"
240
321
  else
241
322
  'List<Any>'
242
323
  end
243
- when Hash then "#{class_prefix[0].upcase}#{class_prefix[1..-1]}"
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
- 'null'
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
- "struct #{class_name}: Codable {\n"
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
- "\n init(\n#{params.map { |p| " #{p}"}.join(",\n")}) {\n#{params.map { |p| " self.#{p.split(':').first} = #{p.split(':').first}" }.join("\n")}\n }\n\n"
281
- end
282
-
283
- def instance_declaration(class_name, json)
284
- " static let shared = #{class_name}(\n#{json.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n"
285
- end
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
- def json_methods(json, class_name)
288
- " func toJson() -> String? {\n" +
289
- " let encoder = JSONEncoder()\n" +
290
- " if let jsonData = try? encoder.encode(self) {\n" +
291
- " return String(data: jsonData, encoding: .utf8)\n" +
292
- " }\n" +
293
- " return nil\n" +
294
- " }\n\n" +
295
- " static func fromJson(_ json: String) -> #{class_name}? {\n" +
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 value.start_with?('#') && value.length == 7 # Assume it's a color
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
- "#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item(\n#{indent} #{item_values}\n#{indent} )"
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
- "#{class_prefix[0].upcase}#{class_prefix[1..-1]}.shared"
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 then '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
- "[#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item]"
487
+ "[#{@class_registry.get("#{capitalize(class_prefix)}Item")}]"
360
488
  else
361
489
  '[Any]'
362
490
  end
363
- when Hash then "#{class_prefix[0].upcase}#{class_prefix[1..-1]}"
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
- r, g, b = value[1..2].to_i(16), value[3..4].to_i(16), value[5..6].to_i(16)
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
- 'nil'
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 'package:flutter/material.dart';\n" +
381
- "import 'dart:convert';\n\n"
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
- "class #{class_name} {\n"
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
- params.empty? ? "\n const #{class_name}();\n\n" : "\n const #{class_name}({\n#{params.map { |p| " #{p}"}.join(",\n")}});\n\n"
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#{json.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n );\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
- " Map<String, dynamic> toJson() => {\n" +
411
- " #{json.keys.map { |k| "'#{k}': #{k}" }.join(",\n ")}\n" +
412
- " };\n\n" +
413
- " factory #{class_name}.fromJson(Map<String, dynamic> json) => #{class_name}(\n" +
414
- " #{json.keys.map { |k| "#{k}: json['#{k}']" }.join(",\n ")}\n" +
415
- " );\n\n" +
416
- " String toJsonString() => jsonEncode(toJson());\n\n" +
417
- " factory #{class_name}.fromJsonString(String jsonString) =>\n" +
418
- " #{class_name}.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);\n"
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 value.start_with?('#') && value.length == 7 # Assume it's a color
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
- "#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item(\n#{indent} #{item_values}\n#{indent} )"
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
- "#{class_prefix[0].upcase}#{class_prefix[1..-1]}.instance"
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 then '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<#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item>"
688
+ "List<#{@class_registry.get("#{capitalize(class_prefix)}Item")}>"
476
689
  else
477
690
  'List<dynamic>'
478
691
  end
479
- when Hash then "#{class_prefix[0].upcase}#{class_prefix[1..-1]}"
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
- 'null'
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