@@ -1039,52 +1039,100 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
10391039
10401040void EditorExportPlatformAndroid::_fix_themes_xml (const Ref<EditorExportPreset> &p_preset) {
10411041 const String themes_xml_path = ExportTemplateManager::get_android_build_directory (p_preset).path_join (" res/values/themes.xml" );
1042- bool enable_swipe_to_dismiss = p_preset->get (" gesture/swipe_to_dismiss" );
10431042
10441043 if (!FileAccess::exists (themes_xml_path)) {
10451044 print_error (" res/values/themes.xml does not exist." );
10461045 return ;
10471046 }
10481047
1049- String xml_content;
1048+ // Default/Reserved theme attributes.
1049+ Dictionary main_theme_attributes;
1050+ main_theme_attributes[" android:windowDrawsSystemBarBackgrounds" ] = " false" ;
1051+ main_theme_attributes[" android:windowSwipeToDismiss" ] = bool_to_string (p_preset->get (" gesture/swipe_to_dismiss" ));
1052+
1053+ Dictionary splash_theme_attributes;
1054+ splash_theme_attributes[" android:windowSplashScreenBackground" ] = " @mipmap/icon_background" ;
1055+ splash_theme_attributes[" windowSplashScreenAnimatedIcon" ] = " @mipmap/icon_foreground" ;
1056+ splash_theme_attributes[" postSplashScreenTheme" ] = " @style/GodotAppMainTheme" ;
1057+
1058+ Dictionary custom_theme_attributes = p_preset->get (" gradle_build/custom_theme_attributes" );
1059+
1060+ // Does not override default/reserved theme attributes; skips any duplicates from custom_theme_attributes.
1061+ for (const Variant &k : custom_theme_attributes.keys ()) {
1062+ String key = k;
1063+ String value = custom_theme_attributes[k];
1064+ if (key.begins_with (" [splash]" )) {
1065+ String splash_key = key.trim_prefix (" [splash]" );
1066+ if (splash_theme_attributes.has (splash_key)) {
1067+ WARN_PRINT (vformat (" Skipped custom_theme_attribute '%s'; this is a reserved attribute configured via other export options or project settings." , splash_key));
1068+ } else {
1069+ splash_theme_attributes[splash_key] = value;
1070+ }
1071+ } else {
1072+ if (main_theme_attributes.has (key)) {
1073+ WARN_PRINT (vformat (" Skipped custom_theme_attribute '%s'; this is a reserved attribute configured via other export options or project settings." , key));
1074+ } else {
1075+ main_theme_attributes[key] = value;
1076+ }
1077+ }
1078+ }
1079+
10501080 Ref<FileAccess> file = FileAccess::open (themes_xml_path, FileAccess::READ);
10511081 PackedStringArray lines = file->get_as_text ().split (" \n " );
10521082 file->close ();
10531083
1054- // Check if the themes.xml already contains <item name="android:windowSwipeToDismiss"> element.
1055- // If found, update its value based on `enable_swipe_to_dismiss`.
1056- bool found = false ;
1057- bool modified = false ;
1084+ PackedStringArray new_lines;
1085+ bool inside_main_theme = false ;
1086+ bool inside_splash_theme = false ;
1087+
10581088 for (int i = 0 ; i < lines.size (); i++) {
10591089 String line = lines[i];
1060- if (line.contains (" <item name" ) && line.contains (" \" android:windowSwipeToDismiss\" >" )) {
1061- lines.set (i, vformat (" <item name=\" android:windowSwipeToDismiss\" >%s</item>" , bool_to_string (enable_swipe_to_dismiss)));
1062- found = true ;
1063- modified = true ;
1064- break ;
1090+
1091+ if (line.contains (" <style name=\" GodotAppMainTheme\" " )) {
1092+ inside_main_theme = true ;
1093+ new_lines.append (line);
1094+ continue ;
1095+ }
1096+ if (line.contains (" <style name=\" GodotAppSplashTheme\" " )) {
1097+ inside_splash_theme = true ;
1098+ new_lines.append (line);
1099+ continue ;
10651100 }
1066- }
10671101
1068- // If <item name="android:windowSwipeToDismiss"> is not found and `enable_swipe_to_dismiss` is false:
1069- // Add a new <item> element before the closing </style> tag.
1070- if (!found && !enable_swipe_to_dismiss) {
1071- for (int i = 0 ; i < lines.size (); i++) {
1072- if (lines[i].contains (" </style>" )) {
1073- lines.insert (i, " <item name=\" android:windowSwipeToDismiss\" >false</item>" );
1074- modified = true ;
1075- break ;
1102+ // Inject GodotAppMainTheme attributes.
1103+ if (inside_main_theme && line.contains (" </style>" )) {
1104+ for (const Variant &attribute : main_theme_attributes.keys ()) {
1105+ String value = main_theme_attributes[attribute];
1106+ String item_line = vformat (" <item name=\" %s\" >%s</item>" , attribute, value);
1107+ new_lines.append (item_line);
1108+ }
1109+ new_lines.append (line); // Add </style> in the end.
1110+ inside_main_theme = false ;
1111+ continue ;
1112+ }
1113+
1114+ // Inject GodotAppSplashTheme attributes.
1115+ if (inside_splash_theme && line.contains (" </style>" )) {
1116+ for (const Variant &attribute : splash_theme_attributes.keys ()) {
1117+ String value = splash_theme_attributes[attribute];
1118+ String item_line = vformat (" <item name=\" %s\" >%s</item>" , attribute, value);
1119+ new_lines.append (item_line);
10761120 }
1121+ new_lines.append (line); // Add </style> in the end.
1122+ inside_splash_theme = false ;
1123+ continue ;
1124+ }
1125+
1126+ // Add all other lines unchanged.
1127+ if (!inside_main_theme && !inside_splash_theme) {
1128+ new_lines.append (line);
10771129 }
10781130 }
10791131
10801132 // Reconstruct the XML content from the modified lines.
1081- if (modified) {
1082- xml_content = String (" \n " ).join (lines);
1083- store_string_at_path (themes_xml_path, xml_content);
1084- print_verbose (" Successfully modified " + themes_xml_path + " : " + " \n " + xml_content);
1085- } else {
1086- print_verbose (" No changes needed for " + themes_xml_path);
1087- }
1133+ String xml_content = String (" \n " ).join (new_lines);
1134+ store_string_at_path (themes_xml_path, xml_content);
1135+ print_verbose (" Successfully modified " + themes_xml_path + " : " + " \n " + xml_content);
10881136}
10891137
10901138void 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
19942042 }
19952043 }
19962044 }
2045+ } else if (p_name == " gradle_build/custom_theme_attributes" ) {
2046+ bool gradle_build_enabled = p_preset->get (" gradle_build/use_gradle_build" );
2047+ if (bool (p_preset->get (" gradle_build/custom_theme_attributes" )) && !gradle_build_enabled) {
2048+ return TTR (" \" Use Gradle Build\" is required to add custom theme attributes." );
2049+ }
19972050 } else if (p_name == " package/show_in_android_tv" ) {
19982051 bool gradle_build_enabled = p_preset->get (" gradle_build/use_gradle_build" );
19992052 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
20272080 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 ));
20282081 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 ));
20292082
2083+ r_options->push_back (ExportOption (PropertyInfo (Variant::DICTIONARY, " gradle_build/custom_theme_attributes" , PROPERTY_HINT_DICTIONARY_TYPE, " String;String" ), Dictionary ()));
2084+
20302085#ifndef DISABLE_DEPRECATED
20312086 Vector<PluginConfigAndroid> plugins_configs = get_plugins ();
20322087 for (int i = 0 ; i < plugins_configs.size (); i++) {
@@ -2108,6 +2163,7 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor
21082163
21092164 bool advanced_options_enabled = p_preset->are_advanced_options_enabled ();
21102165 if (p_option == " graphics/opengl_debug" ||
2166+ p_option == " gradle_build/custom_theme_attributes" ||
21112167 p_option == " command_line/extra_args" ||
21122168 p_option == " permissions/custom_permissions" ||
21132169 p_option == " keystore/debug" ||
0 commit comments