diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java index 65c68b525b..4ba25c4c56 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java @@ -6,6 +6,7 @@ import android.app.Activity; import android.content.Context; import android.preference.PreferenceFragment; +import android.util.TypedValue; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toolbar; @@ -24,12 +25,15 @@ * This class is responsible for injecting our own fragment by replacing the LicenseActivity. */ @SuppressWarnings("unused") -public class LicenseActivityHook { +public class LicenseActivityHook extends Activity { private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value. private static ViewGroup.LayoutParams toolbarLayoutParams; + @SuppressLint("StaticFieldLeak") + public static SearchViewController searchViewController; + public static void setToolbarLayoutParams(Toolbar toolbar) { if (toolbarLayoutParams != null) { toolbar.setLayoutParams(toolbarLayoutParams); @@ -126,12 +130,13 @@ private static void createToolbar(Activity activity, PreferenceFragment fragment view -> view instanceof TextView); if (toolbarTextView != null) { toolbarTextView.setTextColor(Utils.getAppForegroundColor()); + toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); } setToolbarLayoutParams(toolbar); - // Add Search Icon and EditText for ReVancedPreferenceFragment only. + // Add Search bar only for ReVancedPreferenceFragment. if (fragment instanceof ReVancedPreferenceFragment) { - SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment); + searchViewController = SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment); } toolBarParent.addView(toolbar, 0); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/SearchViewController.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/SearchViewController.java index c194108a83..b99c885cf7 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/SearchViewController.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/SearchViewController.java @@ -10,6 +10,7 @@ import android.util.Pair; import android.view.MenuItem; import android.view.View; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; @@ -51,6 +52,7 @@ public class SearchViewController { private final Deque searchHistory; private final AutoCompleteTextView autoCompleteTextView; private final boolean showSettingsSearchHistory; + private int currentOrientation; /** * Creates a background drawable for the SearchView with rounded corners. @@ -83,8 +85,8 @@ public static int getSearchViewBackground() { /** * Adds search view components to the activity. */ - public static void addSearchViewComponents(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) { - new SearchViewController(activity, toolbar, fragment); + public static SearchViewController addSearchViewComponents(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) { + return new SearchViewController(activity, toolbar, fragment); } private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) { @@ -115,6 +117,9 @@ private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPrefere searchView.getContext().getResources().getIdentifier( "android:id/search_src_text", null, null)); + // Disable fullscreen keyboard mode. + autoCompleteTextView.setImeOptions(autoCompleteTextView.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + // Set background and query hint. searchView.setBackground(createBackgroundDrawable(toolbar.getContext())); searchView.setQueryHint(str("revanced_settings_search_hint")); @@ -197,12 +202,14 @@ public boolean onQueryTextChange(String newText) { if (isSearchActive) { closeSearch(); } else { - activity.onBackPressed(); + activity.finish(); } } catch (Exception ex) { Logger.printException(() -> "navigation click failure", ex); } }); + + monitorOrientationChanges(); } /** @@ -285,6 +292,21 @@ private void updateSearchHistoryAdapter() { } } + private void monitorOrientationChanges() { + currentOrientation = activity.getResources().getConfiguration().orientation; + + searchView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { + int newOrientation = activity.getResources().getConfiguration().orientation; + if (newOrientation != currentOrientation) { + currentOrientation = newOrientation; + if (autoCompleteTextView != null) { + autoCompleteTextView.dismissDropDown(); + Logger.printDebug(() -> "Orientation changed, search history dismissed"); + } + } + }); + } + /** * Opens the search view and shows the keyboard. */ @@ -313,7 +335,7 @@ private void openSearch() { /** * Closes the search view and hides the keyboard. */ - private void closeSearch() { + public void closeSearch() { isSearchActive = false; toolbar.getMenu().findItem(getResourceIdentifier( "action_search", "id")).setVisible(true); @@ -326,6 +348,19 @@ private void closeSearch() { imm.hideSoftInputFromWindow(searchView.getWindowToken(), 0); } + public static boolean handleBackPress() { + if (LicenseActivityHook.searchViewController != null + && LicenseActivityHook.searchViewController.isSearchExpanded()) { + LicenseActivityHook.searchViewController.closeSearch(); + return true; + } + return false; + } + + public boolean isSearchExpanded() { + return isSearchActive; + } + /** * Custom ArrayAdapter for search history. */ diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java index ed2608e5af..e764514170 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -17,6 +17,7 @@ import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.BackgroundColorSpan; +import android.util.TypedValue; import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsets; @@ -248,7 +249,15 @@ private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) { rootView.setOnApplyWindowInsetsListener((v, insets) -> { Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars()); Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars()); - v.setPadding(0, statusInsets.top, 0, navInsets.bottom); + Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout()); + + // Apply padding for display cutout in landscape. + int leftPadding = cutoutInsets.left; + int rightPadding = cutoutInsets.right; + int topPadding = statusInsets.top; + int bottomPadding = navInsets.bottom; + + v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); return insets; }); } @@ -265,10 +274,16 @@ private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) { true, TextView.class::isInstance); if (toolbarTextView != null) { toolbarTextView.setTextColor(Utils.getAppForegroundColor()); + toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); } LicenseActivityHook.setToolbarLayoutParams(toolbar); + if (LicenseActivityHook.searchViewController != null + && LicenseActivityHook.searchViewController.isSearchExpanded()) { + toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch()); + } + rootView.addView(toolbar, 0); return false; } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt index fce41f3275..1e6732446e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt @@ -12,31 +12,15 @@ import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.settings.overrideThemeColors -import app.revanced.patches.shared.misc.settings.preference.BasePreference -import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen -import app.revanced.patches.shared.misc.settings.preference.InputType -import app.revanced.patches.shared.misc.settings.preference.IntentPreference -import app.revanced.patches.shared.misc.settings.preference.ListPreference -import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference -import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory -import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference +import app.revanced.patches.shared.misc.settings.preference.* import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting -import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.patches.shared.misc.settings.preference.TextPreference import app.revanced.patches.shared.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.check.checkEnvironmentPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.fix.playbackspeed.fixPlaybackSpeedWhilePlayingPatch import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater import app.revanced.patches.youtube.misc.playservice.versionCheckPatch -import app.revanced.util.ResourceGroup -import app.revanced.util.addInstructionsAtControlFlowLabel -import app.revanced.util.copyResources -import app.revanced.util.copyXmlNode -import app.revanced.util.findElementByAttributeValueOrThrow -import app.revanced.util.findInstructionIndicesReversedOrThrow -import app.revanced.util.inputStreamFromBundledResource -import app.revanced.util.insertLiteralOverride +import app.revanced.util.* import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation @@ -152,15 +136,24 @@ private val settingsResourcePatch = resourcePatch { } } - // Modify the manifest and add a data intent filter to the LicenseActivity. - // Some devices freak out if undeclared data is passed to an intent, - // and this change appears to fix the issue. + // Modify the manifest to enhance LicenseActivity behavior: + // 1. Add a data intent filter with MIME type "text/plain". + // Some devices crash if undeclared data is passed to an intent, + // and this change appears to fix the issue. + // 2. Add android:configChanges="orientation|screenSize|keyboardHidden". + // This prevents the activity from being recreated on configuration changes + // (e.g., screen rotation), preserving its current state and fragment. document("AndroidManifest.xml").use { document -> val licenseElement = document.childNodes.findElementByAttributeValueOrThrow( "android:name", "com.google.android.libraries.social.licenses.LicenseActivity", ) + licenseElement.setAttribute( + "android:configChanges", + "orientation|screenSize|keyboardHidden" + ) + val mimeType = document.createElement("data") mimeType.setAttribute("android:mimeType", "text/plain") @@ -267,6 +260,32 @@ val settingsPatch = bytecodePatch( methods.add(attachBaseContext) } + licenseActivityOnCreateFingerprint.classDef.apply { + val onBackPressed = ImmutableMethod( + type, + "onBackPressed", + emptyList(), + "V", + AccessFlags.PUBLIC.value, + null, + null, + MutableMethodImplementation(3) + ).toMutable().apply { + addInstructions( + """ + invoke-static {}, Lapp/revanced/extension/youtube/settings/SearchViewController;->handleBackPress()Z + move-result v0 + if-nez v0, :search_handled + invoke-virtual { p0 }, Landroid/app/Activity;->finish()V + :search_handled + return-void + """ + ) + + }; + methods.add(onBackPressed); + }; + // Update shared dark mode status based on YT theme. // This is needed because YT allows forcing light/dark mode // which then differs from the system dark mode status. @@ -338,20 +357,18 @@ object PreferenceScreen : BasePreferenceScreen() { icon = "@drawable/revanced_settings_screen_05_player", layout = "@layout/preference_with_icon", ) - val SHORTS = Screen( key = "revanced_settings_screen_06_shorts", summaryKey = null, icon = "@drawable/revanced_settings_screen_06_shorts", layout = "@layout/preference_with_icon", ) - val SEEKBAR = Screen( key = "revanced_settings_screen_07_seekbar", summaryKey = null, icon = "@drawable/revanced_settings_screen_07_seekbar", layout = "@layout/preference_with_icon", - ) + ) val SWIPE_CONTROLS = Screen( key = "revanced_settings_screen_08_swipe_controls", summaryKey = null,