1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Android: Add export option for custom theme attributes

- Regenerates the `GodotAppMainTheme` and `GodotAppSplashTheme` during Android export. Any manual changes to these styles will be cleared and replaced with default theme attributes.

- Adds a new export option `gradle_build/custom_theme_attributes` for injecting custom theme attributes directly via the export window, avoiding the need to manually modify themes.xml.
This commit is contained in:
Anish Mishra
2025-05-22 22:52:14 +05:30
parent 4a44078451
commit 0312a0cc9e
3 changed files with 99 additions and 42 deletions

View File

@@ -52,6 +52,12 @@
<member name="gradle_build/android_source_template" type="String" setter="" getter=""> <member name="gradle_build/android_source_template" type="String" setter="" getter="">
Path to a ZIP file holding the source for the export template used in a Gradle build. If left empty, the default template is used. Path to a ZIP file holding the source for the export template used in a Gradle build. If left empty, the default template is used.
</member> </member>
<member name="gradle_build/custom_theme_attributes" type="Dictionary" setter="" getter="">
A dictionary of custom theme attributes to include in the exported Android project. Each entry defines a theme attribute name and its value, and will be added to the [b]GodotAppMainTheme[/b].
For example, the key [code]android:windowSwipeToDismiss[/code] with the value [code]false[/code] is resolved to [code]&lt;item name="android:windowSwipeToDismiss"&gt;false&lt;/item&gt;[/code].
[b]Note:[/b] To add a custom attribute to the [b]GodotAppSplashTheme[/b], prefix the attribute name with [code][splash][/code].
[b]Note:[/b] Reserved attributes configured via other export options or project settings cannot be overridden by [code]custom_theme_attributes[/code] and are skipped during export.
</member>
<member name="gradle_build/export_format" type="int" setter="" getter=""> <member name="gradle_build/export_format" type="int" setter="" getter="">
Application export format (*.apk or *.aab). Application export format (*.apk or *.aab).
</member> </member>

View File

@@ -1039,52 +1039,100 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
void EditorExportPlatformAndroid::_fix_themes_xml(const Ref<EditorExportPreset> &p_preset) { void EditorExportPlatformAndroid::_fix_themes_xml(const Ref<EditorExportPreset> &p_preset) {
const String themes_xml_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join("res/values/themes.xml"); const String themes_xml_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join("res/values/themes.xml");
bool enable_swipe_to_dismiss = p_preset->get("gesture/swipe_to_dismiss");
if (!FileAccess::exists(themes_xml_path)) { if (!FileAccess::exists(themes_xml_path)) {
print_error("res/values/themes.xml does not exist."); print_error("res/values/themes.xml does not exist.");
return; return;
} }
String xml_content; // Default/Reserved theme attributes.
Ref<FileAccess> file = FileAccess::open(themes_xml_path, FileAccess::READ); Dictionary main_theme_attributes;
PackedStringArray lines = file->get_as_text().split("\n"); main_theme_attributes["android:windowDrawsSystemBarBackgrounds"] = "false";
file->close(); main_theme_attributes["android:windowSwipeToDismiss"] = bool_to_string(p_preset->get("gesture/swipe_to_dismiss"));
// Check if the themes.xml already contains <item name="android:windowSwipeToDismiss"> element. Dictionary splash_theme_attributes;
// If found, update its value based on `enable_swipe_to_dismiss`. splash_theme_attributes["android:windowSplashScreenBackground"] = "@mipmap/icon_background";
bool found = false; splash_theme_attributes["windowSplashScreenAnimatedIcon"] = "@mipmap/icon_foreground";
bool modified = false; splash_theme_attributes["postSplashScreenTheme"] = "@style/GodotAppMainTheme";
for (int i = 0; i < lines.size(); i++) {
String line = lines[i];
if (line.contains("<item name") && line.contains("\"android:windowSwipeToDismiss\">")) {
lines.set(i, vformat(" <item name=\"android:windowSwipeToDismiss\">%s</item>", bool_to_string(enable_swipe_to_dismiss)));
found = true;
modified = true;
break;
}
}
// If <item name="android:windowSwipeToDismiss"> is not found and `enable_swipe_to_dismiss` is false: Dictionary custom_theme_attributes = p_preset->get("gradle_build/custom_theme_attributes");
// Add a new <item> element before the closing </style> tag.
if (!found && !enable_swipe_to_dismiss) { // Does not override default/reserved theme attributes; skips any duplicates from custom_theme_attributes.
for (int i = 0; i < lines.size(); i++) { for (const Variant &k : custom_theme_attributes.keys()) {
if (lines[i].contains("</style>")) { String key = k;
lines.insert(i, " <item name=\"android:windowSwipeToDismiss\">false</item>"); String value = custom_theme_attributes[k];
modified = true; if (key.begins_with("[splash]")) {
break; String splash_key = key.trim_prefix("[splash]");
if (splash_theme_attributes.has(splash_key)) {
WARN_PRINT(vformat("Skipped custom_theme_attribute '%s'; this is a reserved attribute configured via other export options or project settings.", splash_key));
} else {
splash_theme_attributes[splash_key] = value;
}
} else {
if (main_theme_attributes.has(key)) {
WARN_PRINT(vformat("Skipped custom_theme_attribute '%s'; this is a reserved attribute configured via other export options or project settings.", key));
} else {
main_theme_attributes[key] = value;
} }
} }
} }
// Reconstruct the XML content from the modified lines. Ref<FileAccess> file = FileAccess::open(themes_xml_path, FileAccess::READ);
if (modified) { PackedStringArray lines = file->get_as_text().split("\n");
xml_content = String("\n").join(lines); file->close();
store_string_at_path(themes_xml_path, xml_content);
print_verbose("Successfully modified " + themes_xml_path + ": " + "\n" + xml_content); PackedStringArray new_lines;
} else { bool inside_main_theme = false;
print_verbose("No changes needed for " + themes_xml_path); bool inside_splash_theme = false;
for (int i = 0; i < lines.size(); i++) {
String line = lines[i];
if (line.contains("<style name=\"GodotAppMainTheme\"")) {
inside_main_theme = true;
new_lines.append(line);
continue;
}
if (line.contains("<style name=\"GodotAppSplashTheme\"")) {
inside_splash_theme = true;
new_lines.append(line);
continue;
}
// Inject GodotAppMainTheme attributes.
if (inside_main_theme && line.contains("</style>")) {
for (const Variant &attribute : main_theme_attributes.keys()) {
String value = main_theme_attributes[attribute];
String item_line = vformat(" <item name=\"%s\">%s</item>", attribute, value);
new_lines.append(item_line);
}
new_lines.append(line); // Add </style> in the end.
inside_main_theme = false;
continue;
}
// Inject GodotAppSplashTheme attributes.
if (inside_splash_theme && line.contains("</style>")) {
for (const Variant &attribute : splash_theme_attributes.keys()) {
String value = splash_theme_attributes[attribute];
String item_line = vformat(" <item name=\"%s\">%s</item>", attribute, value);
new_lines.append(item_line);
}
new_lines.append(line); // Add </style> in the end.
inside_splash_theme = false;
continue;
}
// Add all other lines unchanged.
if (!inside_main_theme && !inside_splash_theme) {
new_lines.append(line);
}
} }
// Reconstruct the XML content from the modified lines.
String xml_content = String("\n").join(new_lines);
store_string_at_path(themes_xml_path, xml_content);
print_verbose("Successfully modified " + themes_xml_path + ": " + "\n" + xml_content);
} }
void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) { void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {
@@ -1994,6 +2042,11 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport
} }
} }
} }
} else if (p_name == "gradle_build/custom_theme_attributes") {
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
if (bool(p_preset->get("gradle_build/custom_theme_attributes")) && !gradle_build_enabled) {
return TTR("\"Use Gradle Build\" is required to add custom theme attributes.");
}
} else if (p_name == "package/show_in_android_tv") { } else if (p_name == "package/show_in_android_tv") {
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
if (bool(p_preset->get("package/show_in_android_tv")) && !gradle_build_enabled) { if (bool(p_preset->get("package/show_in_android_tv")) && !gradle_build_enabled) {
@@ -2027,6 +2080,8 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_MIN_SDK_VERSION)), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/min_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_MIN_SDK_VERSION)), "", false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "gradle_build/target_sdk", PROPERTY_HINT_PLACEHOLDER_TEXT, vformat("%d (default)", DEFAULT_TARGET_SDK_VERSION)), "", false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "gradle_build/custom_theme_attributes", PROPERTY_HINT_DICTIONARY_TYPE, "String;String"), Dictionary()));
#ifndef DISABLE_DEPRECATED #ifndef DISABLE_DEPRECATED
Vector<PluginConfigAndroid> plugins_configs = get_plugins(); Vector<PluginConfigAndroid> plugins_configs = get_plugins();
for (int i = 0; i < plugins_configs.size(); i++) { for (int i = 0; i < plugins_configs.size(); i++) {
@@ -2108,6 +2163,7 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor
bool advanced_options_enabled = p_preset->are_advanced_options_enabled(); bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
if (p_option == "graphics/opengl_debug" || if (p_option == "graphics/opengl_debug" ||
p_option == "gradle_build/custom_theme_attributes" ||
p_option == "command_line/extra_args" || p_option == "command_line/extra_args" ||
p_option == "permissions/custom_permissions" || p_option == "permissions/custom_permissions" ||
p_option == "keystore/debug" || p_option == "keystore/debug" ||

View File

@@ -1,22 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- GodotAppMainTheme is auto-generated during export. Manual changes will be overwritten.
To add custom attributes, use the "gradle_build/custom_theme_attributes" Android export option. -->
<style name="GodotAppMainTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar"> <style name="GodotAppMainTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowSwipeToDismiss">false</item> <item name="android:windowSwipeToDismiss">false</item>
</style> </style>
<!-- GodotAppSplashTheme is auto-generated during export. Manual changes will be overwritten.
To add custom attributes, use the "gradle_build/custom_theme_attributes" Android export option. -->
<style name="GodotAppSplashTheme" parent="Theme.SplashScreen"> <style name="GodotAppSplashTheme" parent="Theme.SplashScreen">
<!-- Set the splash screen background, animated icon, and animation
duration. -->
<item name="android:windowSplashScreenBackground">@mipmap/icon_background</item> <item name="android:windowSplashScreenBackground">@mipmap/icon_background</item>
<!-- Use windowSplashScreenAnimatedIcon to add a drawable or an animated
drawable. One of these is required. -->
<item name="windowSplashScreenAnimatedIcon">@mipmap/icon_foreground</item> <item name="windowSplashScreenAnimatedIcon">@mipmap/icon_foreground</item>
<!-- Set the theme of the Activity that directly follows your splash
screen. This is required. -->
<item name="postSplashScreenTheme">@style/GodotAppMainTheme</item> <item name="postSplashScreenTheme">@style/GodotAppMainTheme</item>
</style> </style>
</resources> </resources>