Browse Source

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.
Anish Mishra 3 months ago
parent
commit
0312a0cc9e

+ 6 - 0
platform/android/doc_classes/EditorExportPlatformAndroid.xml

@@ -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>

+ 83 - 27
platform/android/export/export_plugin.cpp

@@ -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.
+	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"));
+
+	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";
+
+	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;
+			}
+		}
+	}
+
 	Ref<FileAccess> file = FileAccess::open(themes_xml_path, FileAccess::READ);
 	Ref<FileAccess> file = FileAccess::open(themes_xml_path, FileAccess::READ);
 	PackedStringArray lines = file->get_as_text().split("\n");
 	PackedStringArray lines = file->get_as_text().split("\n");
 	file->close();
 	file->close();
 
 
-	// Check if the themes.xml already contains <item name="android:windowSwipeToDismiss"> element.
-	// If found, update its value based on `enable_swipe_to_dismiss`.
-	bool found = false;
-	bool modified = false;
+	PackedStringArray new_lines;
+	bool inside_main_theme = false;
+	bool inside_splash_theme = false;
+
 	for (int i = 0; i < lines.size(); i++) {
 	for (int i = 0; i < lines.size(); i++) {
 		String line = lines[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 (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;
 		}
 		}
-	}
 
 
-	// If <item name="android:windowSwipeToDismiss"> is not found and `enable_swipe_to_dismiss` is false:
-	// Add a new <item> element before the closing </style> tag.
-	if (!found && !enable_swipe_to_dismiss) {
-		for (int i = 0; i < lines.size(); i++) {
-			if (lines[i].contains("</style>")) {
-				lines.insert(i, "		<item name=\"android:windowSwipeToDismiss\">false</item>");
-				modified = true;
-				break;
+		// 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.
 	// 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);
-	}
+	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" ||

+ 4 - 9
platform/android/java/app/res/values/themes.xml

@@ -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>