solara 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/solara/lib/core/.DS_Store +0 -0
  3. data/solara/lib/core/aliases/alias_generator.rb +1 -0
  4. data/solara/lib/core/aliases/alias_generator_manager.rb +2 -1
  5. data/solara/lib/core/aliases/solara_terminal_setup.rb +1 -1
  6. data/solara/lib/core/brands/brand_font_switcher.rb +154 -0
  7. data/solara/lib/core/brands/brand_onboarder.rb +8 -6
  8. data/solara/lib/core/brands/brand_switcher.rb +232 -166
  9. data/solara/lib/core/brands/brands_manager.rb +15 -39
  10. data/solara/lib/core/dashboard/brand/BrandDetail.js +19 -0
  11. data/solara/lib/core/dashboard/brand/BrandDetailController.js +50 -8
  12. data/solara/lib/core/dashboard/brand/BrandDetailModel.js +9 -30
  13. data/solara/lib/core/dashboard/brand/BrandDetailView.js +49 -3
  14. data/solara/lib/core/dashboard/brand/InfoPlistStringCatalogManager.js +19 -0
  15. data/solara/lib/core/dashboard/brand/brand.html +209 -62
  16. data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +1 -1
  17. data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +38 -53
  18. data/solara/lib/core/dashboard/brands/BrandsController.js +6 -5
  19. data/solara/lib/core/dashboard/brands/BrandsModel.js +2 -2
  20. data/solara/lib/core/dashboard/brands/BrandsView.js +2 -2
  21. data/solara/lib/core/dashboard/brands/brands.html +3 -1
  22. data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +7 -5
  23. data/solara/lib/core/dashboard/dashboard_manager.rb +2 -0
  24. data/solara/lib/core/dashboard/dashboard_server.rb +1 -3
  25. data/solara/lib/core/dashboard/handler/brand_alisases_handler.rb +4 -11
  26. data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +11 -11
  27. data/solara/lib/core/dashboard/handler/{brand_configurations_handler.rb → brand_details_handler.rb} +4 -4
  28. data/solara/lib/core/dashboard/handler/brands_handler.rb +1 -1
  29. data/solara/lib/core/dashboard/handler/edit_section_handler.rb +8 -8
  30. data/solara/lib/core/doctor/brand_doctor.rb +31 -31
  31. data/solara/lib/core/doctor/doctor_manager.rb +0 -1
  32. data/solara/lib/core/doctor/project_doctor.rb +0 -1
  33. data/solara/lib/core/doctor/schema/platform/android/android_config.json +0 -4
  34. data/solara/lib/core/doctor/schema/platform/ios/InfoPlist.xcstrings +15 -0
  35. data/solara/lib/core/doctor/schema/platform/ios/ios_config.json +0 -5
  36. data/solara/lib/core/doctor/schema/platform/shared/brand_config.json +9 -0
  37. data/solara/lib/core/doctor/schema/platform/shared/theme.json +94 -9
  38. data/solara/lib/core/doctor/validator/brand_settings_validator.rb +6 -0
  39. data/solara/lib/core/doctor/validator/brand_settings_validator_manager.rb +9 -21
  40. data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +22 -4
  41. data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +22 -6
  42. data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +22 -4
  43. data/solara/lib/core/scripts/brand_config_generator.rb +1 -0
  44. data/solara/lib/core/scripts/brand_config_manager.rb +2 -4
  45. data/solara/lib/core/scripts/brand_exporter.rb +1 -1
  46. data/solara/lib/core/scripts/brand_importer.rb +2 -3
  47. data/solara/lib/core/scripts/brand_offboarder.rb +5 -0
  48. data/solara/lib/core/scripts/brand_resources_manager.rb +127 -54
  49. data/solara/lib/core/scripts/directory_creator.rb +2 -2
  50. data/solara/lib/core/scripts/file_manager.rb +53 -19
  51. data/solara/lib/core/scripts/file_path.rb +175 -30
  52. data/solara/lib/core/scripts/folder_copier.rb +3 -7
  53. data/solara/lib/core/scripts/gitignore_manager.rb +21 -10
  54. data/solara/lib/core/scripts/interactive_file_system_validator.rb +8 -2
  55. data/solara/lib/core/scripts/platform/android/android_manifest_switcher.rb +3 -3
  56. data/solara/lib/core/scripts/platform/android/android_strings_switcher.rb +26 -24
  57. data/solara/lib/core/scripts/platform/android/gradle_switcher.rb +7 -6
  58. data/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb +123 -0
  59. data/solara/lib/core/scripts/platform/ios/infoplist_switcher.rb +59 -0
  60. data/solara/lib/core/scripts/platform/ios/ios_plist_manager.rb +11 -20
  61. data/solara/lib/core/scripts/platform/ios/plist_font_manager.rb +33 -0
  62. data/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb +15 -3
  63. data/solara/lib/core/scripts/platform/ios/xcode_asset_manager.rb +2 -3
  64. data/solara/lib/core/scripts/platform/ios/xcode_project_manager.rb +80 -1
  65. data/solara/lib/core/scripts/platform/ios/xcode_project_switcher.rb +15 -34
  66. data/solara/lib/core/scripts/solara_logger.rb +10 -0
  67. data/solara/lib/core/scripts/solara_settings_manager.rb +26 -1
  68. data/solara/lib/core/scripts/strings_xml_manager.rb +17 -2
  69. data/solara/lib/core/scripts/theme_generator.rb +133 -130
  70. data/solara/lib/core/scripts/yaml_manager.rb +23 -0
  71. data/solara/lib/core/solara_configurator.rb +1 -1
  72. data/solara/lib/core/template/brands/android/android_config.json +0 -1
  73. data/solara/lib/core/template/brands/android/res/values/strings.xml +4 -0
  74. data/solara/lib/core/template/brands/ios/InfoPlist.xcstrings +30 -0
  75. data/solara/lib/core/template/brands/ios/ios_config.json +1 -2
  76. data/solara/lib/core/template/brands/shared/brand_config.json +1 -0
  77. data/solara/lib/core/template/brands/shared/theme.json +3 -3
  78. data/solara/lib/core/template/config/android_template_config.json +11 -1
  79. data/solara/lib/core/template/config/flutter_template_config.json +12 -2
  80. data/solara/lib/core/template/config/ios_template_config.json +11 -1
  81. data/solara/lib/core/template/project_template_generator.rb +8 -3
  82. data/solara/lib/solara/version.rb +1 -1
  83. data/solara/lib/solara.rb +8 -4
  84. data/solara/lib/solara_initializer.rb +5 -2
  85. data/solara/lib/solara_manager.rb +1 -1
  86. metadata +62 -43
  87. data/solara/lib/core/dashboard/handler/brand_section_handler.rb +0 -20
  88. data/solara/lib/core/doctor/validator/directory_structure_validator.rb +0 -38
  89. data/solara/lib/core/doctor/validator/file_structure_validator.rb +0 -37
  90. data/solara/lib/core/scripts/platform/ios/ios_file_path_manager.rb +0 -109
  91. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/.DS_Store +0 -0
  92. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/100.png +0 -0
  93. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/102.png +0 -0
  94. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/1024.png +0 -0
  95. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/114.png +0 -0
  96. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/120.png +0 -0
  97. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/128.png +0 -0
  98. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/144.png +0 -0
  99. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/152.png +0 -0
  100. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/16.png +0 -0
  101. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/167.png +0 -0
  102. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/172.png +0 -0
  103. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/180.png +0 -0
  104. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/196.png +0 -0
  105. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/20.png +0 -0
  106. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/216.png +0 -0
  107. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/256.png +0 -0
  108. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/29.png +0 -0
  109. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/32.png +0 -0
  110. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/40.png +0 -0
  111. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/48.png +0 -0
  112. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/50.png +0 -0
  113. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/512.png +0 -0
  114. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/55.png +0 -0
  115. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/57.png +0 -0
  116. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/58.png +0 -0
  117. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/60.png +0 -0
  118. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/64.png +0 -0
  119. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/66.png +0 -0
  120. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/72.png +0 -0
  121. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/76.png +0 -0
  122. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/80.png +0 -0
  123. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/87.png +0 -0
  124. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/88.png +0 -0
  125. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/92.png +0 -0
  126. /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/Contents.json +0 -0
@@ -6,6 +6,14 @@ module FilePath
6
6
  SolaraSettingsManager.instance.project_root
7
7
  end
8
8
 
9
+ def self.artifacts_dir_name
10
+ 'solara_artifacts'
11
+ end
12
+
13
+ def self.artifacts_dir_name_ios
14
+ 'SolaraArtifacts'
15
+ end
16
+
9
17
  def self.root
10
18
  SolaraSettingsManager.instance.root
11
19
  end
@@ -14,6 +22,14 @@ module FilePath
14
22
  "android"
15
23
  end
16
24
 
25
+ def self.test_lab
26
+ File.join(ENV['HOME'], '.solara', 'testlab', 'src')
27
+ end
28
+
29
+ def self.test_lab_cache
30
+ File.join(ENV['HOME'], '.solara', 'testlab', 'cache')
31
+ end
32
+
17
33
  def self.android_project_root
18
34
  case SolaraSettingsManager.instance.platform
19
35
  when Platform::Flutter
@@ -63,20 +79,20 @@ module FilePath
63
79
  File.join(brands, brand_key, platform)
64
80
  end
65
81
 
66
- def self.solara
67
- File.join(Dir.pwd, 'solara')
68
- end
69
-
70
82
  def self.dot_solara
71
- File.join(project_root, 'solara', '.solara')
83
+ File.join(project_root, '.solara')
72
84
  end
73
85
 
74
86
  def self.ios
75
87
  "ios"
76
88
  end
77
89
 
90
+ def self.solara_brand
91
+ File.join(project_root, 'solara', 'brand')
92
+ end
93
+
78
94
  def self.brands
79
- File.join(project_root, 'solara', 'brands')
95
+ File.join(project_root, 'solara', 'brand', 'brands')
80
96
  end
81
97
 
82
98
  def self.brand(brand_key)
@@ -91,6 +107,18 @@ module FilePath
91
107
  File.join(ios_brand_root(brand_key), 'ios_config.json')
92
108
  end
93
109
 
110
+ def self.project_infoplist_string_catalog
111
+ File.join(project_info_plist_directory, 'InfoPlist.xcstrings')
112
+ end
113
+
114
+ def self.brand_infoplist_string_catalog(brand_key)
115
+ File.join(ios_brand_root(brand_key), 'InfoPlist.xcstrings')
116
+ end
117
+
118
+ def self.brand_fonts
119
+ File.join(solara_brand, 'global', 'fonts')
120
+ end
121
+
94
122
  def self.brand_config(brand_key)
95
123
  File.join(brands, brand_key, 'shared', 'brand_config.json')
96
124
  end
@@ -127,26 +155,26 @@ module FilePath
127
155
  def self.generated_config(name, platform)
128
156
  case platform
129
157
  when Platform::Flutter
130
- File.join(flutter_artifacts, name)
158
+ File.join(flutter_lib_artifacts, name)
131
159
  when Platform::Android
132
- File.join(android_main_artifacts, name)
160
+ File.join(android_project_java_artifacts, name)
133
161
  when Platform::IOS
134
- File.join(IOSFilePathManager.instance.artifacts, name)
162
+ File.join(ios_project_root_artifacts, name)
135
163
  else
136
164
  raise ArgumentError, "Invalid platform: #{SolaraSettingsManager.instance.platform}"
137
165
  end
138
166
  end
139
167
 
140
- def self.flutter_artifacts
141
- File.join(project_root, 'lib', 'artifacts')
168
+ def self.flutter_lib_artifacts
169
+ File.join(project_root, 'lib', artifacts_dir_name)
142
170
  end
143
171
 
144
- def self.android_artifacts
145
- File.join(android_project_root, 'artifacts')
172
+ def self.android_project_root_artifacts
173
+ File.join(android_project_root, artifacts_dir_name)
146
174
  end
147
175
 
148
- def self.android_main_artifacts
149
- File.join(android_project_root, 'app', 'src', 'main', 'java', 'artifacts')
176
+ def self.android_project_java_artifacts
177
+ File.join(android_project_root, 'app', 'src', 'main', 'java', artifacts_dir_name)
150
178
  end
151
179
 
152
180
  def self.android_brand_config(brand_key)
@@ -177,28 +205,32 @@ module FilePath
177
205
  File.join(android_project_root, 'app', 'src', 'main', 'assets')
178
206
  end
179
207
 
208
+ def self.android_project_assets_artifacts
209
+ File.join(android_project_assets, artifacts_dir_name)
210
+ end
211
+
180
212
  def self.android_brand_assets(brand_key)
181
213
  File.join(android_brand_root(brand_key), 'assets')
182
214
  end
183
215
 
184
216
  def self.android_generated_properties
185
- File.join(android_artifacts, 'brand.properties')
217
+ File.join(android_project_root_artifacts, 'brand.properties')
186
218
  end
187
219
 
188
- def self.android_res
220
+ def self.android_project_res
189
221
  File.join(android_project_root, 'app', 'src', 'main', 'res')
190
222
  end
191
223
 
192
- def self.android_res_artifacts
193
- File.join(android_project_root, 'app', 'src', 'main', 'artifacts')
224
+ def self.android_project_main_artifacts
225
+ File.join(android_project_root, 'app', 'src', 'main', artifacts_dir_name)
194
226
  end
195
227
 
196
228
  def self.android_artifacts_strings
197
- File.join(android_res_artifacts, 'values', 'strings.xml')
229
+ File.join(android_project_main_artifacts, 'values', 'strings.xml')
198
230
  end
199
231
 
200
232
  def self.android_strings
201
- File.join(android_project_root, 'app', 'src', 'main', 'res', 'values', 'strings.xml')
233
+ File.join(android_project_res, 'values', 'strings.xml')
202
234
  end
203
235
 
204
236
  def self.android_brand_res(brand_key)
@@ -206,19 +238,19 @@ module FilePath
206
238
  end
207
239
 
208
240
  def self.brand_flutter_assets(brand_key)
209
- File.join(brands, brand_key, 'shared', 'assets')
241
+ File.join(brands, brand_key, 'flutter', 'assets')
210
242
  end
211
243
 
212
244
  def self.flutter_assets_artifacts
213
- File.join(project_root, 'assets', 'artifacts')
245
+ File.join(project_root, 'assets', artifacts_dir_name)
214
246
  end
215
247
 
216
248
  def self.pub_spec_yaml
217
249
  File.join(project_root, 'pubspec.yaml')
218
250
  end
219
251
 
220
- def self.flutter_artifacts_config
221
- File.join(flutter_artifacts, 'app_config.dart')
252
+ def self.flutter_lib_artifacts_config
253
+ File.join(flutter_lib_artifacts, 'app_config.dart')
222
254
  end
223
255
 
224
256
  def self.dashboard
@@ -233,10 +265,6 @@ module FilePath
233
265
  File.join(brands, 'current_brand.json')
234
266
  end
235
267
 
236
- def self.solara_template_brands(platform)
237
- File.join(solara_template, 'brands', platform)
238
- end
239
-
240
268
  def self.template_brands
241
269
  File.join(solara_template, 'brands')
242
270
  end
@@ -301,13 +329,130 @@ module FilePath
301
329
  when Platform::Android
302
330
  return android_launcher_icon(brand_key)
303
331
  when Platform::IOS
304
- path = IOSFilePathManager.instance.brand_app_icon_image(brand_key)
332
+ path = ios_brand_app_icon_image(brand_key)
305
333
  return path
306
334
  else
307
335
  raise ArgumentError, "Invalid platform: #{SolaraSettingsManager.instance.platform}"
308
336
  end
309
337
  end
310
338
 
339
+ def self.ios_brand_app_icon(brand_key)
340
+ File.join(ios_brand_xcassets(brand_key), 'AppIcon.appiconset')
341
+ end
342
+
343
+ def self.ios_brand_app_icon_image(brand_key)
344
+ appicon_set_path = ios_brand_app_icon(brand_key)
345
+
346
+ if appicon_set_path.nil?
347
+ raise "Error: AppIcon.appiconset not found for brand #{brand_key}"
348
+ end
349
+
350
+ contents_json_path = File.join(appicon_set_path, 'Contents.json')
351
+
352
+ unless File.exist?(contents_json_path)
353
+ raise "Error: Contents.json not found in AppIcon.appiconset for brand #{brand_key}"
354
+ end
355
+
356
+ contents = JSON.parse(File.read(contents_json_path))
357
+
358
+ largest_image = contents['images'].max_by do |img|
359
+ size = img['size'].scan(/(\d+)x(\d+)/).first&.map(&:to_i)
360
+ size ? size[0] * size[1] : 0
361
+ end
362
+
363
+ if largest_image
364
+ File.join(appicon_set_path, largest_image['filename'])
365
+ else
366
+ raise "No images found in Contents.json for brand #{brand_key}"
367
+ end
368
+ end
369
+
370
+ def self.app_xcconfig(name)
371
+ case SolaraSettingsManager.instance.platform
372
+ when Platform::Flutter
373
+ return File.join(project_root, ios, 'Flutter', name)
374
+ when Platform::IOS
375
+ return File.join(xcode_project_directory, 'XCConfig', name)
376
+ else
377
+ raise ArgumentError, "Invalid platform: #{SolaraSettingsManager.instance.platform}"
378
+ end
379
+ end
380
+
381
+ def self.xcode_project_directory
382
+ Pathname.new(xcode_project).parent.to_s
383
+ end
384
+
385
+ def self.xcode_project
386
+ path = ProjectSettingsManager.instance.value('xcodeproj', Platform::IOS)
387
+ File.join(project_root, path)
388
+ end
389
+
390
+ def self.ios_brand_xcassets(brand_key)
391
+ File.join(brands, brand_key, ios, 'xcassets')
392
+ end
393
+
394
+
395
+ def self.ios_brand_assets(brand_key)
396
+ File.join(brands, brand_key, ios, 'assets')
397
+ end
398
+
399
+ def self.brand_xcconfig
400
+ File.join(ios_project_root_artifacts, 'Brand.xcconfig')
401
+ end
402
+
403
+ def self.ios_project_fonts
404
+ File.join(ios_project_root_artifacts, 'Fonts')
405
+ end
406
+
407
+ def self.android_project_fonts
408
+ File.join(android_project_main_artifacts, 'font')
409
+ end
410
+
411
+ def self.flutter_project_fonts
412
+ File.join(project_root, 'assets', 'solara_fonts')
413
+ end
414
+
415
+ def self.ios_project_root_artifacts
416
+ case SolaraSettingsManager.instance.platform
417
+ when Platform::Flutter
418
+ return File.join(project_root, ios, 'Flutter', artifacts_dir_name_ios)
419
+ when Platform::IOS
420
+ return File.join(xcode_project_directory, artifacts_dir_name_ios)
421
+ else
422
+ raise ArgumentError, "Invalid platform: #{SolaraSettingsManager.instance.platform}"
423
+ end
424
+ end
425
+
426
+ def self.project_info_plist_directory
427
+ Pathname.new(info_plist).parent.to_s
428
+ end
429
+
430
+ def self.info_plist
431
+ path = ProjectSettingsManager.instance.value('Info.plist', Platform::IOS)
432
+ File.join(project_root, path)
433
+ end
434
+
435
+ def self.ios_project_assets_artifacts
436
+ File.join(File.dirname(ios_project_xcassets), 'Assets.xcassets', artifacts_dir_name_ios)
437
+ end
438
+
439
+ def self.ios_project_xcassets
440
+ path = ProjectSettingsManager.instance.value('Assets.xcassets', Platform::IOS)
441
+ File.join(project_root, path)
442
+ end
443
+
444
+ def self.ios_project_original_app_icon
445
+ File.join(ios_project_xcassets, 'AppIcon.appiconset')
446
+ end
447
+
448
+ def self.ios_project_artifacts_assets
449
+ File.join(ios_project_root_artifacts, 'Assets')
450
+ end
451
+
452
+ def self.app_xcconfig_directory
453
+ Pathname.new(app_xcconfig('Debug.xcconfig')).parent.to_s
454
+ end
455
+
311
456
  def self.schema
312
457
  File.join(root, 'core', 'doctor', 'schema')
313
458
  end
@@ -6,14 +6,10 @@ class FolderCopier
6
6
  @destination_path = destination_path
7
7
  end
8
8
 
9
- def copy(delete_if_exists: true)
10
- if File.exist?(@destination_path)
11
- if delete_if_exists
12
- FileUtils.rm_rf(@destination_path)
13
- end
14
- end
15
- FileUtils.mkdir_p(@destination_path)
9
+ def copy
10
+ FileUtils.mkdir_p(@destination_path) unless File.exist?(@destination_path)
16
11
  copy_files_and_folders(@source_path, @destination_path)
12
+ Solara.logger.debug("🚗 Copied\n\t↑ From: #{@source_path} \n\t↓ To: #{@destination_path}")
17
13
  end
18
14
 
19
15
  private
@@ -4,16 +4,27 @@ class GitignoreManager
4
4
  create_gitignore_if_not_exists
5
5
  end
6
6
 
7
- def self.ignore_redundant_files
8
- GitignoreManager.new(FilePath.project_root)
9
- .add_items([
10
- "# Generated by Solara. Ignore redundant brand specific changes.",
11
- "**/artifacts/",
12
- "**/Artifacts/",
13
- "solara/brands/current_app.json",
14
- "solara/.solara/aliases/",
15
- "solara/.solara/solara_settings.json",
16
- ])
7
+ def self.ignore
8
+ Solara.logger.start_step("Exclude Brand-Generated Files and Folders from Git")
9
+
10
+ items = [
11
+ "# Generated by Solara. Ignore redundant brand specific changes.",
12
+ "**/#{FilePath.artifacts_dir_name}/",
13
+ "**/#{FilePath.artifacts_dir_name_ios}/",
14
+ "solara/brand/brands/current_brand.json",
15
+ ".solara/aliases/",
16
+ ".solara/solara_settings.json",
17
+ "solara_fonts",
18
+ ]
19
+
20
+ if Platform.is_flutter || Platform.is_ios
21
+ items << FileManager.get_relative_path_to_root(FilePath.project_infoplist_string_catalog)
22
+ # The excluded InfoPlist.xcstrings maybe at the root. In this case we have to avoid ignoring the brands files.
23
+ items << '!solara/brand/brands/**/InfoPlist.xcstrings'
24
+ end
25
+
26
+ GitignoreManager.new(FilePath.project_root).add_items(items)
27
+ Solara.logger.end_step("Exclude Brand-Generated Files and Folders from Git")
17
28
  end
18
29
 
19
30
  def add_items(items)
@@ -34,13 +34,19 @@ class InteractiveFileSystemValidator
34
34
  validate_required_item(item)
35
35
  end
36
36
  else
37
- ignored = %w[solara/ Artifacts/ Pods/ build/]
37
+ ignored = [
38
+ 'macos/',
39
+ 'solara/',
40
+ "#{FilePath.artifacts_dir_name}/",
41
+ "#{FilePath.artifacts_dir_name_ios}/",
42
+ 'Pods/',
43
+ 'build/']
38
44
 
39
45
  root = File.join(@project_root, item_path)
40
46
  paths = if recursive
41
47
  FileManager.find_files_by_name(root, name)
42
48
  else
43
- Dir.glob(File.join(root, name)).select { |path| File.file?(path) || File.directory?(path) }
49
+ FileManager.find_files_and_directories(root, name)
44
50
  end
45
51
 
46
52
  paths = paths.map { |path| FileManager.get_relative_path(@project_root, path) }
@@ -2,8 +2,8 @@ class AndroidManifestSwitcher
2
2
  def initialize
3
3
  end
4
4
 
5
- def update_manifest(config)
6
- Solara.logger.start_step("Update AndroidManifest")
5
+ def switch(config)
6
+ Solara.logger.start_step("Switch AndroidManifest")
7
7
  manifest_file = FilePath.android_manifest
8
8
  if File.exist?(manifest_file)
9
9
  manifest_content = File.read(manifest_file)
@@ -13,7 +13,7 @@ class AndroidManifestSwitcher
13
13
  else
14
14
  Solara.logger.debug("❌ #{FilePath.android_manifest} not found. Skipping manifest update.")
15
15
  end
16
- Solara.logger.end_step("Update AndroidManifest")
16
+ Solara.logger.end_step("Switch AndroidManifest")
17
17
  end
18
18
 
19
19
  private
@@ -1,39 +1,41 @@
1
1
  class AndroidStringsSwitcher
2
- def initialize
2
+ def initialize(brand_key)
3
+ @brand_key = brand_key
4
+ end
5
+
6
+ def switch
7
+ Solara.logger.log_step("Switch strings.xml") do
8
+ switch_brand_name
9
+ remove_original_app_name_from_strings
10
+ end
3
11
  end
4
12
 
5
- def update(config)
6
- Solara.logger.start_step("Generate artifacts/strings.xml")
7
- strings_file = FilePath.android_artifacts_strings
13
+ private
8
14
 
9
- # Create the file if it doesn't exist
10
- unless File.exist?(strings_file)
11
- FileUtils.mkdir_p(File.dirname(strings_file))
12
- end
15
+ def switch_brand_name
16
+ brand_config_path = FilePath.brand_config(@brand_key)
17
+ brand_config = JSON.parse(File.read(brand_config_path))
18
+ strings_path = FilePath.android_artifacts_strings
19
+ manager = StringsXmlManager.new(strings_path)
20
+ app_name_value = manager.get_value('app_name')
13
21
 
14
- strings_content = generate_strings_xml_content(config)
15
- File.write(strings_file, strings_content)
16
- Solara.logger.debug("Updated #{strings_file} with name: \"#{config['brandName']}\"")
22
+ # If user has specified a brandName in brand_config, we don't override it.
23
+ if !app_name_value.nil? && !app_name_value.empty?
24
+ Solara.logger.debug("App name is specified in #{strings_path}. Skipping updating from #{brand_config_path}!")
25
+ return
26
+ end
17
27
 
18
- remove_app_name_from_strings
19
- Solara.logger.end_step("Generate artifacts/strings.xml")
28
+ manager.update_string_value('app_name', brand_config['brandName'])
29
+ Solara.logger.debug("Updated app anme in #{strings_path} from #{brand_config_path}.")
20
30
  end
21
31
 
22
32
  # It's important to delete app_name to avoid duplicate resources
23
- def remove_app_name_from_strings
33
+ def remove_original_app_name_from_strings
24
34
  file_path = FilePath.android_strings
35
+ return unless File.exist?(file_path)
36
+
25
37
  manager = StringsXmlManager.new(file_path)
26
38
  manager.delete_app_name
27
39
  end
28
40
 
29
- private
30
-
31
- def generate_strings_xml_content(config)
32
- <<-XML
33
- <?xml version="1.0" encoding="utf-8"?>
34
- <resources>
35
- <string name="app_name">#{config['brandName']}</string>
36
- </resources>
37
- XML
38
- end
39
41
  end
@@ -1,3 +1,4 @@
1
+ Dir[File.expand_path('../../*.rb', __dir__)].each { |file| require_relative file }
1
2
  require 'fileutils'
2
3
  require 'json'
3
4
 
@@ -10,14 +11,14 @@ import java.util.Properties
10
11
 
11
12
  KOTLIN_PROPERTIES_LOADER = <<-KOTLIN
12
13
  val brandProperties = Properties().apply {
13
- load(FileInputStream(file("../artifacts/brand.properties")))
14
+ load(FileInputStream(file("../#{FilePath.artifacts_dir_name}/brand.properties")))
14
15
  }
15
16
  KOTLIN
16
17
 
17
18
  GROOVY_PROPERTIES_LOADER = <<-GROOVY
18
19
  project.ext {
19
20
  brandProperties = new Properties()
20
- brandProperties.load(new FileInputStream(file("../artifacts/brand.properties")))
21
+ brandProperties.load(new FileInputStream(file("../#{FilePath.artifacts_dir_name}/brand.properties")))
21
22
  }
22
23
  GROOVY
23
24
 
@@ -30,7 +31,7 @@ project.ext {
30
31
  KOTLIN_VERSION_CODE = 'versionCode = brandProperties.getProperty("versionCode").toInt()'
31
32
  GROOVY_VERSION_CODE = "versionCode = project.ext.brandProperties.getProperty('versionCode').toInteger()"
32
33
 
33
- DEFAULT_SOURCE_SETS = %w[src/main/res src/main/artifacts]
34
+ DEFAULT_SOURCE_SETS = ['src/main/res', "src/main/#{FilePath.artifacts_dir_name}"]
34
35
 
35
36
  def initialize(brand_key)
36
37
  @brand_key = brand_key
@@ -39,15 +40,15 @@ project.ext {
39
40
  @source_sets = (@brand_config['sourceSets'] || []).concat(DEFAULT_SOURCE_SETS).uniq
40
41
  end
41
42
 
42
- def update_build_gradle
43
- Solara.logger.start_step("Update app/build.gradle")
43
+ def switch
44
+ Solara.logger.start_step("Switch app/build.gradle")
44
45
  gradle_file = FilePath.android_app_gradle
45
46
  gradle_content = File.read(gradle_file)
46
47
 
47
48
  update_gradle(gradle_file, gradle_content)
48
49
  add_source_sets(gradle_file)
49
50
  update_keystore_config(gradle_file)
50
- Solara.logger.end_step("Update app/build.gradle")
51
+ Solara.logger.end_step("Switch app/build.gradle")
51
52
  end
52
53
 
53
54
  private
@@ -0,0 +1,123 @@
1
+ require 'json'
2
+
3
+ module StringCatalogUtils
4
+ def load_string_catalog(path)
5
+ JSON.parse(File.read(path))
6
+ end
7
+
8
+ def get_value(data, key, target, language)
9
+ lang = language || data['sourceLanguage']
10
+ data['strings'][key]['localizations'][lang]['stringUnit'][target]
11
+ end
12
+
13
+ def has_value?(data, key, language)
14
+ state = get_value(data, key, 'state', language)
15
+ value = get_value(data, key, 'value', language)
16
+ state != 'new' && value != ''
17
+ end
18
+ end
19
+
20
+ class InfoPListStringCatalogManager
21
+ include StringCatalogUtils
22
+
23
+ def initialize(string_catalog_path)
24
+ @string_catalog_path = string_catalog_path
25
+ @data = load_string_catalog(@string_catalog_path)
26
+ end
27
+
28
+ def update(result, can_remove_extra_values: true)
29
+
30
+ update_localizations(result)
31
+
32
+ remove_unused_localizations(result) if can_remove_extra_values
33
+
34
+ File.write(@string_catalog_path, JSON.pretty_generate(@data))
35
+ end
36
+
37
+ def source_language
38
+ @data['sourceLanguage']
39
+ end
40
+
41
+ def get(key, language: nil)
42
+ get_value(@data, key, 'value', language)
43
+ end
44
+
45
+ private
46
+
47
+ def update_localizations(result)
48
+ result.each do |base_key, languages|
49
+ next unless @data['strings'].key?(base_key)
50
+
51
+ languages.each do |lang_code, value|
52
+ initialize_localization(base_key, lang_code)
53
+ update_string_unit(base_key, lang_code, value)
54
+ end
55
+ end
56
+ end
57
+
58
+ def initialize_localization(base_key, lang_code)
59
+ @data['strings'][base_key]['localizations'][lang_code] ||= {
60
+ "stringUnit" => {
61
+ "state" => "new",
62
+ "value" => ""
63
+ }
64
+ }
65
+ end
66
+
67
+ def update_string_unit(base_key, lang_code, value)
68
+ state = value.empty? ? 'new' : 'translated'
69
+ @data['strings'][base_key]['localizations'][lang_code]['stringUnit'].merge!({
70
+ 'state' => state,
71
+ 'value' => value
72
+ })
73
+ end
74
+
75
+ def remove_unused_localizations(result)
76
+ @data['strings'].each do |base_key, data|
77
+ data['localizations'].each_key do |lang_code|
78
+ data['localizations'].delete(lang_code) unless result.dig(base_key, lang_code)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ class InfoPListStringCatalogValidator
85
+ include StringCatalogUtils
86
+
87
+ def initialize(string_catalog_path)
88
+ @string_catalog_path = string_catalog_path
89
+ @data = load_string_catalog(@string_catalog_path)
90
+ @source_language = @data['sourceLanguage']
91
+ end
92
+
93
+ def validate
94
+ issues = []
95
+
96
+ @data['strings'].keys.each do |key|
97
+ languages = @data['strings'][key]['localizations'].keys
98
+ languages.each do |language|
99
+ validation_error = validate_key(key, language)
100
+ if validation_error
101
+ issues << (language == @source_language ? Issue.error(validation_error) : Issue.warning(validation_error))
102
+ end
103
+ end
104
+ end
105
+
106
+ issues
107
+ end
108
+
109
+ private
110
+
111
+ def validate_key(key, language)
112
+ return nil if has_value?(@data, key, language)
113
+
114
+ "The value for '#{key}' is not translated in #{@string_catalog_path}. " \
115
+ "To resolve this issue, please open the dashboard and update the entry for '#{key}.#{@source_language}' in section 'iOS InfoPlist.xcstrings Configuration'. " \
116
+ "If you prefer, you can manually add a value and mark its state as 'translated'."
117
+ end
118
+ end
119
+
120
+ module InfoPListKey
121
+ BUNDLE_NAME = 'CFBundleName'
122
+ BUNDLE_DISPLAY_NAME = 'CFBundleDisplayName'
123
+ end