From 0312a0cc9e12fd9bf0d37aa3e9957b2e3788a77b Mon Sep 17 00:00:00 2001 From: Anish Mishra Date: Thu, 22 May 2025 22:52:14 +0530 Subject: [PATCH] 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. --- .../EditorExportPlatformAndroid.xml | 6 + platform/android/export/export_plugin.cpp | 122 +++++++++++++----- .../android/java/app/res/values/themes.xml | 13 +- 3 files changed, 99 insertions(+), 42 deletions(-) diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index 66ec7c042ec..62ee39fbb98 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -52,6 +52,12 @@ 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. + + 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]<item name="android:windowSwipeToDismiss">false</item>[/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. + Application export format (*.apk or *.aab). diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 7aef7e1269c..e0ac999bc18 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1039,52 +1039,100 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref &p_preset) { 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)) { print_error("res/values/themes.xml does not exist."); return; } - String xml_content; - Ref file = FileAccess::open(themes_xml_path, FileAccess::READ); - PackedStringArray lines = file->get_as_text().split("\n"); - file->close(); + // Default/Reserved theme attributes. + Dictionary main_theme_attributes; + main_theme_attributes["android:windowDrawsSystemBarBackgrounds"] = "false"; + main_theme_attributes["android:windowSwipeToDismiss"] = bool_to_string(p_preset->get("gesture/swipe_to_dismiss")); - // Check if the themes.xml already contains element. - // If found, update its value based on `enable_swipe_to_dismiss`. - bool found = false; - bool modified = false; - for (int i = 0; i < lines.size(); i++) { - String line = lines[i]; - if (line.contains("")) { - lines.set(i, vformat(" %s", bool_to_string(enable_swipe_to_dismiss))); - found = true; - modified = true; - break; - } - } + Dictionary splash_theme_attributes; + splash_theme_attributes["android:windowSplashScreenBackground"] = "@mipmap/icon_background"; + splash_theme_attributes["windowSplashScreenAnimatedIcon"] = "@mipmap/icon_foreground"; + splash_theme_attributes["postSplashScreenTheme"] = "@style/GodotAppMainTheme"; - // If is not found and `enable_swipe_to_dismiss` is false: - // Add a new element before the closing tag. - if (!found && !enable_swipe_to_dismiss) { - for (int i = 0; i < lines.size(); i++) { - if (lines[i].contains("")) { - lines.insert(i, " false"); - modified = true; - break; + Dictionary custom_theme_attributes = p_preset->get("gradle_build/custom_theme_attributes"); + + // Does not override default/reserved theme attributes; skips any duplicates from custom_theme_attributes. + for (const Variant &k : custom_theme_attributes.keys()) { + String key = k; + String value = custom_theme_attributes[k]; + if (key.begins_with("[splash]")) { + 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. - if (modified) { - xml_content = String("\n").join(lines); - store_string_at_path(themes_xml_path, xml_content); - print_verbose("Successfully modified " + themes_xml_path + ": " + "\n" + xml_content); - } else { - print_verbose("No changes needed for " + themes_xml_path); + Ref file = FileAccess::open(themes_xml_path, FileAccess::READ); + PackedStringArray lines = file->get_as_text().split("\n"); + file->close(); + + PackedStringArray new_lines; + bool inside_main_theme = false; + bool inside_splash_theme = false; + + for (int i = 0; i < lines.size(); i++) { + String line = lines[i]; + + if (line.contains(" in the end. + inside_main_theme = false; + continue; + } + + // Inject GodotAppSplashTheme attributes. + if (inside_splash_theme && line.contains("")) { + for (const Variant &attribute : splash_theme_attributes.keys()) { + String value = splash_theme_attributes[attribute]; + String item_line = vformat(" %s", attribute, value); + new_lines.append(item_line); + } + new_lines.append(line); // Add 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 &p_preset, Vector &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") { 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) { @@ -2027,6 +2080,8 @@ void EditorExportPlatformAndroid::get_export_options(List *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/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 Vector plugins_configs = get_plugins(); 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(); if (p_option == "graphics/opengl_debug" || + p_option == "gradle_build/custom_theme_attributes" || p_option == "command_line/extra_args" || p_option == "permissions/custom_permissions" || p_option == "keystore/debug" || diff --git a/platform/android/java/app/res/values/themes.xml b/platform/android/java/app/res/values/themes.xml index c97054233cb..53e3516154d 100644 --- a/platform/android/java/app/res/values/themes.xml +++ b/platform/android/java/app/res/values/themes.xml @@ -1,22 +1,17 @@ - + +