solara 0.2.0 → 0.2.2
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/core/.DS_Store +0 -0
- data/solara/lib/core/aliases/alias_generator.rb +1 -0
- data/solara/lib/core/aliases/alias_generator_manager.rb +2 -1
- data/solara/lib/core/aliases/solara_terminal_setup.rb +1 -1
- data/solara/lib/core/brands/brand_font_switcher.rb +154 -0
- data/solara/lib/core/brands/brand_onboarder.rb +8 -6
- data/solara/lib/core/brands/brand_switcher.rb +232 -166
- data/solara/lib/core/brands/brands_manager.rb +15 -39
- data/solara/lib/core/dashboard/brand/BrandDetail.js +19 -0
- data/solara/lib/core/dashboard/brand/BrandDetailController.js +50 -8
- data/solara/lib/core/dashboard/brand/BrandDetailModel.js +9 -30
- data/solara/lib/core/dashboard/brand/BrandDetailView.js +49 -3
- data/solara/lib/core/dashboard/brand/InfoPlistStringCatalogManager.js +19 -0
- data/solara/lib/core/dashboard/brand/brand.html +209 -62
- data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +1 -1
- data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +38 -53
- data/solara/lib/core/dashboard/brands/BrandsController.js +6 -5
- data/solara/lib/core/dashboard/brands/BrandsModel.js +2 -2
- data/solara/lib/core/dashboard/brands/BrandsView.js +2 -2
- data/solara/lib/core/dashboard/brands/brands.html +3 -1
- data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +7 -5
- data/solara/lib/core/dashboard/dashboard_manager.rb +2 -0
- data/solara/lib/core/dashboard/dashboard_server.rb +13 -8
- data/solara/lib/core/dashboard/handler/brand_alisases_handler.rb +4 -11
- data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +11 -11
- data/solara/lib/core/dashboard/handler/{brand_configurations_handler.rb → brand_details_handler.rb} +4 -4
- data/solara/lib/core/dashboard/handler/brands_handler.rb +1 -1
- data/solara/lib/core/dashboard/handler/edit_section_handler.rb +8 -8
- data/solara/lib/core/doctor/brand_doctor.rb +31 -31
- data/solara/lib/core/doctor/doctor_manager.rb +0 -1
- data/solara/lib/core/doctor/project_doctor.rb +0 -1
- data/solara/lib/core/doctor/schema/platform/android/android_config.json +0 -4
- data/solara/lib/core/doctor/schema/platform/ios/InfoPlist.xcstrings +15 -0
- data/solara/lib/core/doctor/schema/platform/ios/ios_config.json +0 -5
- data/solara/lib/core/doctor/schema/platform/shared/brand_config.json +9 -0
- data/solara/lib/core/doctor/schema/platform/shared/theme.json +94 -9
- data/solara/lib/core/doctor/validator/brand_settings_validator.rb +6 -0
- data/solara/lib/core/doctor/validator/brand_settings_validator_manager.rb +9 -21
- data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +22 -4
- data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +22 -6
- data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +22 -4
- data/solara/lib/core/scripts/brand_config_generator.rb +1 -0
- data/solara/lib/core/scripts/brand_config_manager.rb +2 -4
- data/solara/lib/core/scripts/brand_exporter.rb +1 -1
- data/solara/lib/core/scripts/brand_importer.rb +2 -3
- data/solara/lib/core/scripts/brand_offboarder.rb +5 -0
- data/solara/lib/core/scripts/brand_resources_manager.rb +127 -54
- data/solara/lib/core/scripts/directory_creator.rb +2 -2
- data/solara/lib/core/scripts/file_manager.rb +53 -19
- data/solara/lib/core/scripts/file_path.rb +175 -30
- data/solara/lib/core/scripts/folder_copier.rb +3 -7
- data/solara/lib/core/scripts/gitignore_manager.rb +21 -10
- data/solara/lib/core/scripts/interactive_file_system_validator.rb +8 -2
- data/solara/lib/core/scripts/platform/android/android_manifest_switcher.rb +3 -3
- data/solara/lib/core/scripts/platform/android/android_strings_switcher.rb +26 -24
- data/solara/lib/core/scripts/platform/android/gradle_switcher.rb +7 -6
- data/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb +123 -0
- data/solara/lib/core/scripts/platform/ios/infoplist_switcher.rb +59 -0
- data/solara/lib/core/scripts/platform/ios/ios_plist_manager.rb +11 -20
- data/solara/lib/core/scripts/platform/ios/plist_font_manager.rb +33 -0
- data/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb +15 -3
- data/solara/lib/core/scripts/platform/ios/xcode_asset_manager.rb +2 -3
- data/solara/lib/core/scripts/platform/ios/xcode_project_manager.rb +80 -1
- data/solara/lib/core/scripts/platform/ios/xcode_project_switcher.rb +15 -34
- data/solara/lib/core/scripts/solara_logger.rb +10 -0
- data/solara/lib/core/scripts/solara_settings_manager.rb +26 -1
- data/solara/lib/core/scripts/strings_xml_manager.rb +17 -2
- data/solara/lib/core/scripts/theme_generator.rb +133 -130
- data/solara/lib/core/scripts/yaml_manager.rb +23 -0
- data/solara/lib/core/solara_configurator.rb +1 -1
- data/solara/lib/core/template/brands/android/android_config.json +0 -1
- data/solara/lib/core/template/brands/android/res/values/strings.xml +4 -0
- data/solara/lib/core/template/brands/ios/InfoPlist.xcstrings +30 -0
- data/solara/lib/core/template/brands/ios/ios_config.json +1 -2
- data/solara/lib/core/template/brands/shared/brand_config.json +1 -0
- data/solara/lib/core/template/brands/shared/theme.json +3 -3
- data/solara/lib/core/template/config/android_template_config.json +11 -1
- data/solara/lib/core/template/config/flutter_template_config.json +12 -2
- data/solara/lib/core/template/config/ios_template_config.json +11 -1
- data/solara/lib/core/template/project_template_generator.rb +8 -3
- data/solara/lib/solara/version.rb +1 -1
- data/solara/lib/solara.rb +8 -4
- data/solara/lib/solara_initializer.rb +5 -2
- data/solara/lib/solara_manager.rb +1 -1
- metadata +62 -43
- data/solara/lib/core/dashboard/handler/brand_section_handler.rb +0 -20
- data/solara/lib/core/doctor/validator/directory_structure_validator.rb +0 -38
- data/solara/lib/core/doctor/validator/file_structure_validator.rb +0 -37
- data/solara/lib/core/scripts/platform/ios/ios_file_path_manager.rb +0 -109
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/.DS_Store +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/100.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/102.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/1024.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/114.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/120.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/128.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/144.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/152.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/16.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/167.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/172.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/180.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/196.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/20.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/216.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/256.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/29.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/32.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/40.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/48.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/50.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/512.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/55.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/57.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/58.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/60.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/64.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/66.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/72.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/76.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/80.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/87.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/88.png +0 -0
- /data/solara/lib/core/template/brands/ios/{assets → xcassets}/AppIcon.appiconset/92.png +0 -0
- /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, '
|
|
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(
|
|
158
|
+
File.join(flutter_lib_artifacts, name)
|
|
131
159
|
when Platform::Android
|
|
132
|
-
File.join(
|
|
160
|
+
File.join(android_project_java_artifacts, name)
|
|
133
161
|
when Platform::IOS
|
|
134
|
-
File.join(
|
|
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.
|
|
141
|
-
File.join(project_root, 'lib',
|
|
168
|
+
def self.flutter_lib_artifacts
|
|
169
|
+
File.join(project_root, 'lib', artifacts_dir_name)
|
|
142
170
|
end
|
|
143
171
|
|
|
144
|
-
def self.
|
|
145
|
-
File.join(android_project_root,
|
|
172
|
+
def self.android_project_root_artifacts
|
|
173
|
+
File.join(android_project_root, artifacts_dir_name)
|
|
146
174
|
end
|
|
147
175
|
|
|
148
|
-
def self.
|
|
149
|
-
File.join(android_project_root, 'app', 'src', 'main', 'java',
|
|
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(
|
|
217
|
+
File.join(android_project_root_artifacts, 'brand.properties')
|
|
186
218
|
end
|
|
187
219
|
|
|
188
|
-
def self.
|
|
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.
|
|
193
|
-
File.join(android_project_root, 'app', 'src', 'main',
|
|
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(
|
|
229
|
+
File.join(android_project_main_artifacts, 'values', 'strings.xml')
|
|
198
230
|
end
|
|
199
231
|
|
|
200
232
|
def self.android_strings
|
|
201
|
-
File.join(
|
|
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, '
|
|
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',
|
|
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.
|
|
221
|
-
File.join(
|
|
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 =
|
|
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
|
|
10
|
-
|
|
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.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
6
|
-
Solara.logger.start_step("
|
|
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("
|
|
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
|
-
|
|
6
|
-
Solara.logger.start_step("Generate artifacts/strings.xml")
|
|
7
|
-
strings_file = FilePath.android_artifacts_strings
|
|
13
|
+
private
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
Solara.logger.
|
|
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
|
|
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("
|
|
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("
|
|
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 =
|
|
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
|
|
43
|
-
Solara.logger.start_step("
|
|
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("
|
|
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
|