solara 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/solara/lib/.DS_Store +0 -0
- data/solara/lib/core/.DS_Store +0 -0
- data/solara/lib/core/brands/brand_switcher.rb +58 -1
- data/solara/lib/core/dashboard/brand/BrandDetail.js +34 -2
- data/solara/lib/core/dashboard/brand/BrandDetailController.js +23 -233
- data/solara/lib/core/dashboard/brand/BrandDetailModel.js +13 -5
- data/solara/lib/core/dashboard/brand/BrandDetailView.js +16 -200
- data/solara/lib/core/dashboard/brand/SectionsFormManager.js +232 -0
- data/solara/lib/core/dashboard/brand/brand.html +187 -177
- data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +2 -5
- data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +36 -133
- data/solara/lib/core/dashboard/brands/Brands.js +31 -0
- data/solara/lib/core/dashboard/brands/BrandsController.js +0 -5
- data/solara/lib/core/dashboard/brands/BrandsView.js +2 -2
- data/solara/lib/core/dashboard/brands/brands.html +71 -52
- data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +6 -6
- data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +4 -4
- data/solara/lib/core/dashboard/component/ConfirmationDialog.js +15 -10
- data/solara/lib/core/dashboard/component/EditJsonSheet.js +160 -0
- data/solara/lib/core/dashboard/component/MessageBottomSheet.js +5 -5
- data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +5 -3
- data/solara/lib/core/dashboard/handler/base_handler.rb +1 -0
- data/solara/lib/core/dashboard/handler/edit_section_handler.rb +1 -5
- data/solara/lib/core/doctor/schema/brand_configurations.json +0 -8
- data/solara/lib/core/doctor/schema/platform/global/resources_manifest.json +30 -0
- data/solara/lib/core/doctor/schema/platform/json_manifest.json +57 -0
- data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +35 -1
- data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +30 -1
- data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +35 -1
- data/solara/lib/core/doctor/validator/template/template_validator.rb +9 -9
- data/solara/lib/core/scripts/brand_config_manager.rb +1 -1
- data/solara/lib/core/scripts/brand_configurations_manager.rb +41 -0
- data/solara/lib/core/scripts/code_generator.rb +342 -118
- data/solara/lib/core/scripts/file_path.rb +21 -1
- data/solara/lib/core/scripts/gitignore_manager.rb +11 -3
- data/solara/lib/core/scripts/json_manifest_processor.rb +95 -0
- data/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb +11 -1
- data/solara/lib/core/scripts/resource_manifest_processor.rb +151 -0
- data/solara/lib/core/scripts/solara_status_manager.rb +1 -1
- data/solara/lib/core/scripts/theme_generator.rb +21 -242
- data/solara/lib/core/solara_configurator.rb +1 -1
- data/solara/lib/core/template/brands/global/resources_manifest.json +10 -0
- data/solara/lib/core/template/brands/json/Json-Manifest.md +61 -0
- data/solara/lib/core/template/brands/json/json_manifest.json +18 -0
- data/solara/lib/core/template/brands/shared/theme.json +213 -29
- data/solara/lib/core/template/config/android_template_config.json +50 -0
- data/solara/lib/core/template/config/flutter_template_config.json +35 -0
- data/solara/lib/core/template/config/ios_template_config.json +50 -0
- data/solara/lib/core/template/configurations.json +46 -0
- data/solara/lib/core/template/project_template_generator.rb +2 -0
- data/solara/lib/solara/version.rb +1 -1
- data/solara/lib/solara.rb +19 -0
- metadata +13 -4
- data/solara/lib/core/dashboard/component/AddFieldSheet.js +0 -175
- data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +0 -73
@@ -99,6 +99,14 @@ module FilePath
|
|
99
99
|
File.join(brands, brand_key)
|
100
100
|
end
|
101
101
|
|
102
|
+
def self.brand_json_dir(brand_key, platform = nil)
|
103
|
+
File.join(brand(brand_key), platform || SolaraSettingsManager.instance.platform, 'json')
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.brand_global_json_dir
|
107
|
+
File.join(global, 'json')
|
108
|
+
end
|
109
|
+
|
102
110
|
def self.android_config(brand_key)
|
103
111
|
File.join(android_brand_root(brand_key), 'android_config.json')
|
104
112
|
end
|
@@ -116,7 +124,15 @@ module FilePath
|
|
116
124
|
end
|
117
125
|
|
118
126
|
def self.brand_fonts
|
119
|
-
File.join(
|
127
|
+
File.join(global, 'fonts')
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.resources_manifest
|
131
|
+
File.join(global, 'resources_manifest.json')
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.global
|
135
|
+
File.join(solara_brand, 'global')
|
120
136
|
end
|
121
137
|
|
122
138
|
def self.brand_config(brand_key)
|
@@ -287,6 +303,10 @@ module FilePath
|
|
287
303
|
File.join(root, 'core', 'template')
|
288
304
|
end
|
289
305
|
|
306
|
+
def self.brand_configurations
|
307
|
+
File.join(solara_template, 'configurations.json')
|
308
|
+
end
|
309
|
+
|
290
310
|
def self.solara_aliases_json
|
291
311
|
File.join(dot_solara, 'aliases', 'aliases.json')
|
292
312
|
end
|
@@ -4,7 +4,7 @@ class GitignoreManager
|
|
4
4
|
create_gitignore_if_not_exists
|
5
5
|
end
|
6
6
|
|
7
|
-
def self.
|
7
|
+
def self.ignore_common_files
|
8
8
|
Solara.logger.start_step("Exclude Brand-Generated Files and Folders from Git")
|
9
9
|
|
10
10
|
items = [
|
@@ -29,7 +29,7 @@ class GitignoreManager
|
|
29
29
|
|
30
30
|
def add_items(items)
|
31
31
|
items.each do |item|
|
32
|
-
add_item(item)
|
32
|
+
add_item(FileManager.get_relative_path_to_root(item))
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -39,7 +39,15 @@ class GitignoreManager
|
|
39
39
|
if existing_items.include?(item)
|
40
40
|
Solara.logger.debug("'#{item}' already exists in .gitignore")
|
41
41
|
else
|
42
|
-
File.open(@gitignore_path, 'a') do |file|
|
42
|
+
File.open(@gitignore_path, 'a+') do |file|
|
43
|
+
# Move the file pointer to the beginning to check the last character
|
44
|
+
file.seek(0, IO::SEEK_END)
|
45
|
+
if file.size > 0
|
46
|
+
# Only add a new line if the last character is not a newline
|
47
|
+
file.seek(-1, IO::SEEK_END)
|
48
|
+
last_char = file.getc
|
49
|
+
file.puts if last_char != "\n"
|
50
|
+
end
|
43
51
|
file.puts(item)
|
44
52
|
end
|
45
53
|
Solara.logger.debug("Added '#{item}' to .gitignore")
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class JsonManifestProcessor
|
4
|
+
|
5
|
+
def initialize(json_path, language, output_path)
|
6
|
+
@json_path = json_path
|
7
|
+
@language = language
|
8
|
+
@output_path = output_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def process
|
12
|
+
manifest = read_json
|
13
|
+
process_files(manifest)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def process_files(manifest)
|
19
|
+
manifest['files'].each do |file|
|
20
|
+
generate_code(file)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate_code(file)
|
25
|
+
return unless file['generate']['enabled']
|
26
|
+
|
27
|
+
file_name = file['fileName']
|
28
|
+
class_name = file['generate']['className']
|
29
|
+
|
30
|
+
puts "generate_code: file_name = #{file_name}, class_name = #{class_name}"
|
31
|
+
return if file_name.empty? || class_name.empty?
|
32
|
+
puts "generate_code: file_name = #{file_name}, class_name = #{class_name}"
|
33
|
+
|
34
|
+
custom_class_names = convert_to_map(file['generate']['customClassNames'])
|
35
|
+
|
36
|
+
file_path = File.join(@json_path, file_name)
|
37
|
+
code_generator = CodeGenerator.new(
|
38
|
+
json: JSON.parse(File.read(file_path)),
|
39
|
+
language: @language ,
|
40
|
+
parent_class_name: class_name,
|
41
|
+
custom_types: custom_class_names
|
42
|
+
)
|
43
|
+
|
44
|
+
generated_code = code_generator.generate
|
45
|
+
|
46
|
+
output_path = File.join(@output_path, gnerated_filename(class_name))
|
47
|
+
write_to_file(output_path, generated_code)
|
48
|
+
end
|
49
|
+
|
50
|
+
def gnerated_filename(class_name)
|
51
|
+
case SolaraSettingsManager.instance.platform
|
52
|
+
when Platform::Flutter
|
53
|
+
"#{to_snake_case(class_name)}.dart"
|
54
|
+
when Platform::IOS
|
55
|
+
"#{class_name}.swift"
|
56
|
+
when Platform::Android
|
57
|
+
"#{class_name}.kt"
|
58
|
+
else
|
59
|
+
raise ArgumentError, "Invalid platform: #{@platform}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_snake_case(string)
|
64
|
+
string.gsub(/[A-Z]/) { |match| "_#{match.downcase}" }.sub(/^_/, '')
|
65
|
+
end
|
66
|
+
|
67
|
+
def write_to_file(output, content)
|
68
|
+
puts "generate_code: output = #{output}, content = #{content}"
|
69
|
+
File.write(output, content)
|
70
|
+
Solara.logger.debug("Genrated #{output}")
|
71
|
+
rescue StandardError => e
|
72
|
+
Solara.logger.debug("Error writing to file #{file_name}: #{e.message}")
|
73
|
+
end
|
74
|
+
|
75
|
+
def convert_to_map(custom_class_names)
|
76
|
+
custom_class_names.each_with_object({}) do |item, result|
|
77
|
+
result[item['generatedName']] = item['customName']
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def read_json
|
82
|
+
mainfest_path = File.join(@json_path, 'json_manifest.json')
|
83
|
+
JSON.parse(File.read(mainfest_path))
|
84
|
+
rescue JSON::ParserError => e
|
85
|
+
Solara.logger.debug("Error parsing JSON: #{e.message}")
|
86
|
+
{}
|
87
|
+
rescue Errno::ENOENT => e
|
88
|
+
Solara.logger.debug("Error reading file: #{e.message}")
|
89
|
+
{}
|
90
|
+
rescue StandardError => e
|
91
|
+
Solara.logger.debug("Unexpected error: #{e.message}")
|
92
|
+
{}
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -2,12 +2,22 @@ require 'json'
|
|
2
2
|
|
3
3
|
module StringCatalogUtils
|
4
4
|
def load_string_catalog(path)
|
5
|
+
@path = path
|
5
6
|
JSON.parse(File.read(path))
|
6
7
|
end
|
7
8
|
|
8
9
|
def get_value(data, key, target, language)
|
9
10
|
lang = language || data['sourceLanguage']
|
10
|
-
data
|
11
|
+
localizations = data.dig('strings', key, 'localizations', lang)
|
12
|
+
|
13
|
+
unless localizations && localizations['stringUnit']
|
14
|
+
error_message = "The default language is #{lang}, but no localizations are available for key '#{key}'. Please address this issue in {@path}. You can easily open the file in Xcode to make the necessary adjustments."
|
15
|
+
Solara.logger.fatal(error_message)
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
string_unit = localizations['stringUnit']
|
20
|
+
string_unit[target]
|
11
21
|
end
|
12
22
|
|
13
23
|
def has_value?(data, key, language)
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class ResourceManifestProcessor
|
5
|
+
def initialize(brand_key)
|
6
|
+
@brand_key = brand_key
|
7
|
+
@manifest_file = FilePath.resources_manifest
|
8
|
+
@config = load_manifest_file
|
9
|
+
end
|
10
|
+
|
11
|
+
def copy
|
12
|
+
@base_source_path = FilePath.brands
|
13
|
+
@base_destination_path = FilePath.project_root
|
14
|
+
@config['files'].each do |item|
|
15
|
+
process_file_item(item)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def clean(item, src, dst)
|
22
|
+
paths = destinations(item, src, dst)
|
23
|
+
paths.each do |path|
|
24
|
+
File.delete(path) if File.exist?(path)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def process_file_item(item)
|
29
|
+
src = resolve_source_path(@brand_key, item['source'], @base_source_path)
|
30
|
+
dst = File.join(@base_destination_path, item['destination'])
|
31
|
+
|
32
|
+
clean(item, src, dst)
|
33
|
+
|
34
|
+
return skip_empty_paths(item) if empty_paths?(item)
|
35
|
+
|
36
|
+
check_mandatory_file(item, src, dst)
|
37
|
+
|
38
|
+
if File.exist?(src)
|
39
|
+
copy_file(src, dst)
|
40
|
+
git_ignore(destinations(item, src, dst))
|
41
|
+
else
|
42
|
+
log_file_not_found(item)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def destinations(item, src, dst, visited = {})
|
47
|
+
return [] if visited[src]
|
48
|
+
|
49
|
+
visited[src] = true # Mark the current source as visited
|
50
|
+
|
51
|
+
if File.file?(src)
|
52
|
+
if File.directory?(dst)
|
53
|
+
return [File.join(dst, File.basename(src))]
|
54
|
+
else
|
55
|
+
return [dst]
|
56
|
+
end
|
57
|
+
elsif File.directory?(src)
|
58
|
+
return destinations_of_directory_contents(src, dst)
|
59
|
+
end
|
60
|
+
|
61
|
+
if !item['mandatory'] && !File.file?(src)
|
62
|
+
return get_optional_resource_destination(item, dst, visited)
|
63
|
+
end
|
64
|
+
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_optional_resource_destination(item, dst, visited)
|
69
|
+
return [] if item['mandatory']
|
70
|
+
keys = BrandsManager.instance.brands_list.map { |brand| brand['key'] }
|
71
|
+
keys.each do |key|
|
72
|
+
src = resolve_source_path(key, item['source'], @base_source_path)
|
73
|
+
result = destinations(item, src, dst, visited)
|
74
|
+
return result unless result.empty?
|
75
|
+
end
|
76
|
+
[]
|
77
|
+
end
|
78
|
+
|
79
|
+
def destinations_of_directory_contents(src_dir, dst_dir)
|
80
|
+
items = []
|
81
|
+
Dir.foreach(src_dir) do |file|
|
82
|
+
next if file == '.' || file == '..'
|
83
|
+
full_dst_path = File.join(dst_dir, file)
|
84
|
+
items << full_dst_path
|
85
|
+
end
|
86
|
+
items
|
87
|
+
end
|
88
|
+
|
89
|
+
def git_ignore(files)
|
90
|
+
files.each do |file|
|
91
|
+
GitignoreManager.new(FilePath.project_root).add_items([file])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def resolve_source_path(brand_key, source, base_source_path)
|
96
|
+
source.gsub(/\{.*?\}/, brand_key).prepend(base_source_path + '/')
|
97
|
+
end
|
98
|
+
|
99
|
+
def empty_paths?(item)
|
100
|
+
item['source'].empty? || item['destination'].empty?
|
101
|
+
end
|
102
|
+
|
103
|
+
def skip_empty_paths(item)
|
104
|
+
Solara.logger.debug("Skipped (empty source or destination) for #{@brand_key}: #{item['source']} -> #{item['destination']}")
|
105
|
+
end
|
106
|
+
|
107
|
+
def check_mandatory_file(item, src, dst)
|
108
|
+
if item['mandatory'] && !File.exist?(src)
|
109
|
+
Solara.logger.fatal("Mandatory resource file/folder not found for #{@brand_key}: #{src}. Please add the resource or mark it as not mandatory in #{FilePath.resources_manifest}.")
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
def copy_file(src, dst)
|
116
|
+
if File.directory?(src)
|
117
|
+
FileUtils.mkdir_p(dst)
|
118
|
+
FileUtils.cp_r(File.join(src, '.'), dst)
|
119
|
+
else
|
120
|
+
FileUtils.mkdir_p(File.dirname(dst))
|
121
|
+
FileUtils.cp(src, dst)
|
122
|
+
end
|
123
|
+
Solara.logger.debug("Copied resource for #{@brand_key}: #{File.basename(src)} to #{File.basename(dst)}")
|
124
|
+
end
|
125
|
+
|
126
|
+
def log_file_not_found(item)
|
127
|
+
Solara.logger.debug("Skipped resource (not found) for #{@brand_key}: #{item['source']}")
|
128
|
+
end
|
129
|
+
|
130
|
+
def load_manifest_file
|
131
|
+
validate_manifest_file_existence
|
132
|
+
parse_manifest_file
|
133
|
+
end
|
134
|
+
|
135
|
+
def validate_manifest_file_existence
|
136
|
+
unless File.exist?(@manifest_file)
|
137
|
+
Solara.logger.fatal("Brand switch copy manifest not found for #{@brand_key}: #{@manifest_file}")
|
138
|
+
exit 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def parse_manifest_file
|
143
|
+
begin
|
144
|
+
JSON.parse(File.read(@manifest_file))
|
145
|
+
rescue JSON::ParserError => e
|
146
|
+
Solara.logger.fatal("Invalid brand switch copy manifest for #{@brand_key}: #{e.message}")
|
147
|
+
exit 1
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -1,253 +1,32 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'fileutils'
|
3
3
|
|
4
|
-
class
|
4
|
+
class ThemeGenerator
|
5
5
|
def initialize(input_path)
|
6
6
|
@input_path = input_path
|
7
7
|
end
|
8
8
|
|
9
9
|
def generate(language, output_path)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
class ThemeGenerator
|
27
|
-
def initialize(input_path, output_path)
|
28
|
-
@theme = JSON.parse(File.read(input_path))
|
29
|
-
@output_path = output_path
|
30
|
-
end
|
31
|
-
|
32
|
-
def write_to_file(code)
|
33
|
-
FileUtils.mkdir_p(File.dirname(@output_path))
|
34
|
-
File.write(@output_path, code)
|
35
|
-
Solara.logger.debug("Generated theme file: #{@output_path}")
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class KotlinThemeGenerator < ThemeGenerator
|
40
|
-
def generate
|
41
|
-
code = "import android.graphics.Color\n\n"
|
42
|
-
code += "object BrandTheme {\n"
|
43
|
-
code += generate_colors
|
44
|
-
code += generate_typography
|
45
|
-
code += generate_spacing
|
46
|
-
code += generate_border_radius
|
47
|
-
code += generate_elevation
|
48
|
-
code += "}"
|
49
|
-
write_to_file(code)
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def generate_colors
|
55
|
-
code = " object Colors {\n"
|
56
|
-
@theme['colors'].each do |name, value|
|
57
|
-
code += " val #{name} = Color.parseColor(\"#{value}\")\n"
|
58
|
-
end
|
59
|
-
code + " }\n\n"
|
60
|
-
end
|
61
|
-
|
62
|
-
def generate_typography
|
63
|
-
code = " object Typography {\n"
|
64
|
-
code += " object FontFamily {\n"
|
65
|
-
@theme['typography']['fontFamily'].each do |name, value|
|
66
|
-
code += " val #{name} = \"#{value}\"\n"
|
67
|
-
end
|
68
|
-
code += " }\n\n"
|
69
|
-
code += " object FontSize {\n"
|
70
|
-
@theme['typography']['fontSize'].each do |name, value|
|
71
|
-
code += " val #{name} = #{value}\n"
|
72
|
-
end
|
73
|
-
code += " }\n"
|
74
|
-
code + " }\n\n"
|
75
|
-
end
|
76
|
-
|
77
|
-
def generate_spacing
|
78
|
-
code = " object Spacing {\n"
|
79
|
-
@theme['spacing'].each do |name, value|
|
80
|
-
code += " val #{name} = #{value}\n"
|
81
|
-
end
|
82
|
-
code + " }\n\n"
|
83
|
-
end
|
84
|
-
|
85
|
-
def generate_border_radius
|
86
|
-
code = " object BorderRadius {\n"
|
87
|
-
@theme['borderRadius'].each do |name, value|
|
88
|
-
code += " val #{name} = #{value}\n"
|
89
|
-
end
|
90
|
-
code + " }\n\n"
|
91
|
-
end
|
92
|
-
|
93
|
-
def generate_elevation
|
94
|
-
code = " object Elevation {\n"
|
95
|
-
@theme['elevation'].each do |name, value|
|
96
|
-
code += " val #{name} = #{value}\n"
|
97
|
-
end
|
98
|
-
code + " }\n"
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
class SwiftThemeGenerator < ThemeGenerator
|
103
|
-
def generate
|
104
|
-
code = "import UIKit\n\n"
|
105
|
-
code += "struct BrandTheme {\n"
|
106
|
-
code += generate_colors
|
107
|
-
code += generate_typography
|
108
|
-
code += generate_spacing
|
109
|
-
code += generate_border_radius
|
110
|
-
code += generate_elevation
|
111
|
-
code += "}\n\n"
|
112
|
-
code += generate_colors_hex_extension
|
113
|
-
write_to_file(code)
|
114
|
-
end
|
115
|
-
|
116
|
-
private
|
117
|
-
|
118
|
-
def generate_colors
|
119
|
-
code = " struct Colors {\n"
|
120
|
-
@theme['colors'].each do |name, value|
|
121
|
-
code += " static let #{name} = UIColor(hex: \"#{value}\")\n"
|
122
|
-
end
|
123
|
-
code + " }\n\n"
|
124
|
-
end
|
125
|
-
|
126
|
-
def generate_colors_hex_extension
|
127
|
-
<<-SWIFT
|
128
|
-
extension UIColor {
|
129
|
-
convenience init(hex: String) {
|
130
|
-
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
131
|
-
var int: UInt64 = 0
|
132
|
-
Scanner(string: hex).scanHexInt64(&int)
|
133
|
-
let a, r, g, b: UInt64
|
134
|
-
switch hex.count {
|
135
|
-
case 3: // RGB (12-bit)
|
136
|
-
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
|
137
|
-
case 6: // RGB (24-bit)
|
138
|
-
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
|
139
|
-
case 8: // ARGB (32-bit)
|
140
|
-
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
|
141
|
-
default:
|
142
|
-
(a, r, g, b) = (255, 255, 255, 0)
|
10
|
+
config_generator = CodeGenerator.new(
|
11
|
+
json: JSON.parse(File.read(@input_path)),
|
12
|
+
language: language,
|
13
|
+
parent_class_name: 'BrandTheme',
|
14
|
+
custom_types: {
|
15
|
+
"BrandThemeColorSchemes" => "BrandColorScheme",
|
16
|
+
"BrandThemeTypography" => "BrandTypography",
|
17
|
+
"BrandThemeSpacing" => "BrandSpacing",
|
18
|
+
"BrandThemeBorderRadius" => "BrandBorderRadius",
|
19
|
+
"BrandThemeElevation" => "BrandElevation",
|
20
|
+
"BrandThemeOpacity" => "BrandOpacity",
|
21
|
+
"BrandThemeAnimation" => "BrandAnimation",
|
22
|
+
"BrandThemeBreakpoints" => "BrandBreakpoints",
|
23
|
+
"BrandThemeColorSchemesLight" => "BrandLightColorScheme",
|
24
|
+
"BrandThemeColorSchemesDark" => "BrandDarkColorScheme",
|
143
25
|
}
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
alpha: CGFloat(a) / 255
|
150
|
-
)
|
151
|
-
}
|
152
|
-
}
|
153
|
-
SWIFT
|
154
|
-
end
|
155
|
-
|
156
|
-
def generate_typography
|
157
|
-
code = " struct Typography {\n"
|
158
|
-
code += " struct FontFamily {\n"
|
159
|
-
@theme['typography']['fontFamily'].each do |name, value|
|
160
|
-
code += " static let #{name} = \"#{value}\"\n"
|
161
|
-
end
|
162
|
-
code += " }\n\n"
|
163
|
-
code += " struct FontSize {\n"
|
164
|
-
@theme['typography']['fontSize'].each do |name, value|
|
165
|
-
code += " static let #{name}: CGFloat = #{value}\n"
|
166
|
-
end
|
167
|
-
code += " }\n"
|
168
|
-
code + " }\n\n"
|
169
|
-
end
|
170
|
-
|
171
|
-
def generate_spacing
|
172
|
-
code = " struct Spacing {\n"
|
173
|
-
@theme['spacing'].each do |name, value|
|
174
|
-
code += " static let #{name}: CGFloat = #{value}\n"
|
175
|
-
end
|
176
|
-
code + " }\n\n"
|
177
|
-
end
|
178
|
-
|
179
|
-
def generate_border_radius
|
180
|
-
code = " struct BorderRadius {\n"
|
181
|
-
@theme['borderRadius'].each do |name, value|
|
182
|
-
code += " static let #{name}: CGFloat = #{value}\n"
|
183
|
-
end
|
184
|
-
code + " }\n\n"
|
185
|
-
end
|
186
|
-
|
187
|
-
def generate_elevation
|
188
|
-
code = " struct Elevation {\n"
|
189
|
-
@theme['elevation'].each do |name, value|
|
190
|
-
code += " static let #{name}: CGFloat = #{value}\n"
|
191
|
-
end
|
192
|
-
code + " }\n"
|
26
|
+
)
|
27
|
+
content = config_generator.generate
|
28
|
+
FileManager.create_file_if_not_exist(output_path)
|
29
|
+
File.write(output_path, content)
|
30
|
+
Solara.logger.debug("Generated theme file: #{output_path}")
|
193
31
|
end
|
194
32
|
end
|
195
|
-
|
196
|
-
class DartThemeGenerator < ThemeGenerator
|
197
|
-
def generate
|
198
|
-
code = "import 'package:flutter/material.dart';\n\n"
|
199
|
-
code += generate_colors
|
200
|
-
code += generate_typography
|
201
|
-
code += generate_spacing
|
202
|
-
code += generate_border_radius
|
203
|
-
code += generate_elevation
|
204
|
-
write_to_file(code)
|
205
|
-
end
|
206
|
-
|
207
|
-
private
|
208
|
-
|
209
|
-
def generate_colors
|
210
|
-
code = " class BrandColors {\n"
|
211
|
-
@theme['colors'].each do |name, value|
|
212
|
-
code += " static const Color #{name} = Color(0xFF#{value[1..-1]});\n"
|
213
|
-
end
|
214
|
-
code + " }\n\n"
|
215
|
-
end
|
216
|
-
|
217
|
-
def generate_typography
|
218
|
-
code = " class FontFamily {\n"
|
219
|
-
@theme['typography']['fontFamily'].each do |name, value|
|
220
|
-
code += " static const String #{name} = '#{value}';\n"
|
221
|
-
end
|
222
|
-
code += " }\n\n"
|
223
|
-
code += " class FontSize {\n"
|
224
|
-
@theme['typography']['fontSize'].each do |name, value|
|
225
|
-
code += " static const double #{name} = #{value};\n"
|
226
|
-
end
|
227
|
-
code + " }\n\n"
|
228
|
-
end
|
229
|
-
|
230
|
-
def generate_spacing
|
231
|
-
code = " class Spacing {\n"
|
232
|
-
@theme['spacing'].each do |name, value|
|
233
|
-
code += " static const double #{name} = #{value};\n"
|
234
|
-
end
|
235
|
-
code + " }\n\n"
|
236
|
-
end
|
237
|
-
|
238
|
-
def generate_border_radius
|
239
|
-
code = " class BorderRadius {\n"
|
240
|
-
@theme['borderRadius'].each do |name, value|
|
241
|
-
code += " static const double #{name} = #{value};\n"
|
242
|
-
end
|
243
|
-
code += " }\n\n"
|
244
|
-
end
|
245
|
-
|
246
|
-
def generate_elevation
|
247
|
-
code = " class Elevation {\n"
|
248
|
-
@theme['elevation'].each do |name, value|
|
249
|
-
code += " static const double #{name} = #{value};\n"
|
250
|
-
end
|
251
|
-
code + " }\n"
|
252
|
-
end
|
253
|
-
end
|