diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e8dcd5b1..95215edc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,137 @@ +# [5.41.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.17...v5.41.0-dev.18) (2025-09-26) + + +### Bug Fixes + +* **YouTube - Settings:** Handle on screen back swipe gesture ([#6002](https://github.com/ReVanced/revanced-patches/issues/6002)) ([6f92b6c](https://github.com/ReVanced/revanced-patches/commit/6f92b6c50beab091f5f7ef7386579eda38cb4c66)) + +# [5.41.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.16...v5.41.0-dev.17) (2025-09-26) + + +### Bug Fixes + +* **YouTube - SponsorBlock:** Show category color dot in voting dialog menu ([4be00d0](https://github.com/ReVanced/revanced-patches/commit/4be00d09b7b87dcfac324de8709af06e9f730791)) + +# [5.41.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.15...v5.41.0-dev.16) (2025-09-26) + + +### Features + +* **YouTube Music:** Add `Theme` patch ([#5984](https://github.com/ReVanced/revanced-patches/issues/5984)) ([3bd76d6](https://github.com/ReVanced/revanced-patches/commit/3bd76d60d664befff29c24c9de56dac1486a6e67)) + +# [5.41.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.14...v5.41.0-dev.15) (2025-09-25) + + +### Features + +* **YouTube - Hide layout components:** Add "Hide view count" and "Hide upload time" settings ([#5983](https://github.com/ReVanced/revanced-patches/issues/5983)) ([7a37d85](https://github.com/ReVanced/revanced-patches/commit/7a37d858fb937c6bdc2219103dac765b62600e6c)) + +# [5.41.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.13...v5.41.0-dev.14) (2025-09-24) + + +### Features + +* **YouTube - Hide layout components:** Add "Hide Emoji and Timestamp buttons" setting ([#5992](https://github.com/ReVanced/revanced-patches/issues/5992)) ([2b555f6](https://github.com/ReVanced/revanced-patches/commit/2b555f67f07e0de5703c630888ce2fbba3145192)) + +# [5.41.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.12...v5.41.0-dev.13) (2025-09-24) + + +### Bug Fixes + +* **YouTube - Hide Shorts components:** Fix "Hide preview comment" ([#5990](https://github.com/ReVanced/revanced-patches/issues/5990)) ([dd4e2cd](https://github.com/ReVanced/revanced-patches/commit/dd4e2cd0855ccc51b94593004fdd8150ac3b41cc)) + +# [5.41.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.11...v5.41.0-dev.12) (2025-09-24) + + +### Bug Fixes + +* **YouTube - SponsorBlock:** Show category color in create new segment menu ([#5987](https://github.com/ReVanced/revanced-patches/issues/5987)) ([ffd933c](https://github.com/ReVanced/revanced-patches/commit/ffd933c6734274cdde5aaec0159b67f173f9228c)) + +# [5.41.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.10...v5.41.0-dev.11) (2025-09-23) + + +### Features + +* **YouTube:** Add `Disable video codecs` patch ([#5981](https://github.com/ReVanced/revanced-patches/issues/5981)) ([bfbffbd](https://github.com/ReVanced/revanced-patches/commit/bfbffbd1f5aa867027053e25b343a51a606216a3)) + +# [5.41.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.9...v5.41.0-dev.10) (2025-09-23) + + +### Bug Fixes + +* **TikTok:** Show correct dialog restart text, use correct font color for non-dark mode ([d1a1293](https://github.com/ReVanced/revanced-patches/commit/d1a12930c35f630793a0f240d4203c2ff9060158)) + +# [5.41.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.8...v5.41.0-dev.9) (2025-09-23) + + +### Bug Fixes + +* **Instagram - Hide navigation buttons:** Remove button based on name ([#5971](https://github.com/ReVanced/revanced-patches/issues/5971)) ([6fa4043](https://github.com/ReVanced/revanced-patches/commit/6fa404331b5162682d83fba5f38ed570c31495fc)) + +# [5.41.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.7...v5.41.0-dev.8) (2025-09-23) + + +### Features + +* **YouTube Music:** Add `Check watch history domain name resolution` ([#5979](https://github.com/ReVanced/revanced-patches/issues/5979)) ([8af70fe](https://github.com/ReVanced/revanced-patches/commit/8af70fe2d10c0f4da2d7e53bd00f5b3979775d5d)) + +# [5.41.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.6...v5.41.0-dev.7) (2025-09-23) + + +### Features + +* **Tumblr:** Add `Disable Tumblr TV` patch ([#5959](https://github.com/ReVanced/revanced-patches/issues/5959)) ([212418b](https://github.com/ReVanced/revanced-patches/commit/212418b8db9a730ae9efa89ad2bef24952afbadd)) + +# [5.41.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.5...v5.41.0-dev.6) (2025-09-22) + + +### Features + +* **YouTube - Spoof app version:** Add spoof target `20.05.46` that fixes transcript functionality ([5823f0e](https://github.com/ReVanced/revanced-patches/commit/5823f0e982e87b4a35d30feeca8a7e16edfebc5f)) + +# [5.41.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.4...v5.41.0-dev.5) (2025-09-22) + + +### Bug Fixes + +* **Twitch - Settings:** Fix missing style resources ([#5970](https://github.com/ReVanced/revanced-patches/issues/5970)) ([8c22995](https://github.com/ReVanced/revanced-patches/commit/8c229954d7f232a7a472ca49f1b5e7cdc475bbcc)) + +# [5.41.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.3...v5.41.0-dev.4) (2025-09-22) + + +### Bug Fixes + +* **Instagram - Limit feed to followed profiles:** Preserve favorites feed ([#5963](https://github.com/ReVanced/revanced-patches/issues/5963)) ([ef51401](https://github.com/ReVanced/revanced-patches/commit/ef514017f46025d9aef6884424caeb0670514e7a)) + +# [5.41.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.2...v5.41.0-dev.3) (2025-09-22) + + +### Features + +* **YouTube - Loop video:** Add player button to change loop video state ([#5961](https://github.com/ReVanced/revanced-patches/issues/5961)) ([dfb5407](https://github.com/ReVanced/revanced-patches/commit/dfb5407e67222e80e23c8935e04b6dbf1a43d757)) + +# [5.41.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.1...v5.41.0-dev.2) (2025-09-21) + + +### Bug Fixes + +* **YouTube - Spoof video streams:** Update client side effects summary text ([a0a62dd](https://github.com/ReVanced/revanced-patches/commit/a0a62ddad26cfab3e04907fae5532e1ba1fdf710)) + +# [5.41.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.40.1-dev.1...v5.41.0-dev.1) (2025-09-21) + + +### Features + +* **YouTube Music:** Add `Sanitize sharing links` patch ([#5952](https://github.com/ReVanced/revanced-patches/issues/5952)) ([45c1ee8](https://github.com/ReVanced/revanced-patches/commit/45c1ee8a12dc777a371875d90741a05cf5d8e9dd)) + +## [5.40.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.40.0...v5.40.1-dev.1) (2025-09-21) + + +### Bug Fixes + +* **YouTube - Return YouTube Dislike:** Do not show error toast if API returns 401 status ([#5949](https://github.com/ReVanced/revanced-patches/issues/5949)) ([58d088a](https://github.com/ReVanced/revanced-patches/commit/58d088ab307440a6912a867246da799b7dd6499b)) +* **YouTube - Settings:** Use an overlay to show search results ([#5806](https://github.com/ReVanced/revanced-patches/issues/5806)) ([ece8076](https://github.com/ReVanced/revanced-patches/commit/ece8076f7cefd752b97515014bc50fe4fd80171e)) + # [5.40.0](https://github.com/ReVanced/revanced-patches/compare/v5.39.0...v5.40.0) (2025-09-21) diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java index 2367e738a..3154dd9f7 100644 --- a/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java +++ b/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java @@ -10,9 +10,17 @@ public class LimitFeedToFollowedProfiles { * Injection point. */ public static Map setFollowingHeader(Map requestHeaderMap) { + String paginationHeaderName = "pagination_source"; + + // Patch the header only if it's trying to fetch the default feed + String currentHeader = requestHeaderMap.get(paginationHeaderName); + if (currentHeader != null && !currentHeader.equals("feed_recs")) { + return requestHeaderMap; + } + // Create new map as original is unmodifiable. Map patchedRequestHeaderMap = new HashMap<>(requestHeaderMap); - patchedRequestHeaderMap.put("pagination_source", "following"); + patchedRequestHeaderMap.put(paginationHeaderName, "following"); return patchedRequestHeaderMap; } } diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/hide/navigation/HideNavigationButtonsPatch.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/hide/navigation/HideNavigationButtonsPatch.java new file mode 100644 index 000000000..1dd99e204 --- /dev/null +++ b/extensions/instagram/src/main/java/app/revanced/extension/instagram/hide/navigation/HideNavigationButtonsPatch.java @@ -0,0 +1,33 @@ +package app.revanced.extension.instagram.hide.navigation; + +import java.lang.reflect.Field; +import java.util.List; + +@SuppressWarnings("unused") +public class HideNavigationButtonsPatch { + + /** + * Injection point. + * @param navigationButtonsList the list of navigation buttons, as an (obfuscated) Enum type + * @param buttonNameToRemove the name of the button we want to remove + * @param enumNameField the field in the nav button enum class which contains the name of the button + * @return the patched list of navigation buttons + */ + public static List removeNavigationButtonByName( + List navigationButtonsList, + String buttonNameToRemove, + String enumNameField + ) + throws IllegalAccessException, NoSuchFieldException { + for (Object button : navigationButtonsList) { + Field f = button.getClass().getDeclaredField(enumNameField); + String currentButtonEnumName = (String) f.get(button); + + if (buttonNameToRemove.equals(currentButtonEnumName)) { + navigationButtonsList.remove(button); + break; + } + } + return navigationButtonsList; + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/patches/theme/ThemePatch.java b/extensions/music/src/main/java/app/revanced/extension/music/patches/theme/ThemePatch.java new file mode 100644 index 000000000..3f4e39669 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/patches/theme/ThemePatch.java @@ -0,0 +1,27 @@ +package app.revanced.extension.music.patches.theme; + +import app.revanced.extension.shared.theme.BaseThemePatch; + +@SuppressWarnings("unused") +public class ThemePatch extends BaseThemePatch { + + // Color constants used in relation with litho components. + private static final int[] DARK_VALUES = { + 0xFF212121, // Comments box background. + 0xFF030303, // Button container background in album. + 0xFF000000, // Button container background in playlist. + }; + + /** + * Injection point. + *

+ * Change the color of Litho components. + * If the color of the component matches one of the values, return the background color. + * + * @param originalValue The original color value. + * @return The new or original color value. + */ + public static int getValue(int originalValue) { + return processColorValue(originalValue, DARK_VALUES, null); + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/GoogleApiActivityHook.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/GoogleApiActivityHook.java deleted file mode 100644 index 336ea890f..000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/GoogleApiActivityHook.java +++ /dev/null @@ -1,87 +0,0 @@ -package app.revanced.extension.music.settings; - -import android.app.Activity; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.preference.PreferenceFragment; -import android.view.View; - -import app.revanced.extension.music.settings.preference.ReVancedPreferenceFragment; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.BaseActivityHook; - -/** - * Hooks GoogleApiActivity to inject a custom ReVancedPreferenceFragment with a toolbar. - */ -public class GoogleApiActivityHook extends BaseActivityHook { - /** - * Injection point - *

- * Creates an instance of GoogleApiActivityHook for use in static initialization. - */ - @SuppressWarnings("unused") - public static GoogleApiActivityHook createInstance() { - // Must touch the Music settings to ensure the class is loaded and - // the values can be found when setting the UI preferences. - // Logging anything under non debug ensures this is set. - Logger.printInfo(() -> "Permanent repeat enabled: " + Settings.PERMANENT_REPEAT.get()); - - // YT Music always uses dark mode. - Utils.setIsDarkModeEnabled(true); - - return new GoogleApiActivityHook(); - } - - /** - * Sets the fixed theme for the activity. - */ - @Override - protected void customizeActivityTheme(Activity activity) { - // Override the default YouTube Music theme to increase start padding of list items. - // Custom style located in resources/music/values/style.xml - activity.setTheme(Utils.getResourceIdentifier("Theme.ReVanced.YouTubeMusic.Settings", "style")); - } - - /** - * Returns the resource ID for the YouTube Music settings layout. - */ - @Override - protected int getContentViewResourceId() { - return Utils.getResourceIdentifier("revanced_music_settings_with_toolbar", "layout"); - } - - /** - * Returns the fixed background color for the toolbar. - */ - @Override - protected int getToolbarBackgroundColor() { - return Utils.getResourceColor("ytm_color_black"); - } - - /** - * Returns the navigation icon with a color filter applied. - */ - @Override - protected Drawable getNavigationIcon() { - Drawable navigationIcon = ReVancedPreferenceFragment.getBackButtonDrawable(); - navigationIcon.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); - return navigationIcon; - } - - /** - * Returns the click listener that finishes the activity when the navigation icon is clicked. - */ - @Override - protected View.OnClickListener getNavigationClickListener(Activity activity) { - return view -> activity.finish(); - } - - /** - * Creates a new ReVancedPreferenceFragment for the activity. - */ - @Override - protected PreferenceFragment createPreferenceFragment() { - return new ReVancedPreferenceFragment(); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/MusicActivityHook.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/MusicActivityHook.java new file mode 100644 index 000000000..bb19d2497 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/MusicActivityHook.java @@ -0,0 +1,126 @@ +package app.revanced.extension.music.settings; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.preference.PreferenceFragment; +import android.view.View; +import android.widget.Toolbar; + +import app.revanced.extension.music.settings.preference.MusicPreferenceFragment; +import app.revanced.extension.music.settings.search.MusicSearchViewController; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.BaseActivityHook; + +/** + * Hooks GoogleApiActivity to inject a custom {@link MusicPreferenceFragment} with a toolbar and search. + */ +public class MusicActivityHook extends BaseActivityHook { + + @SuppressLint("StaticFieldLeak") + public static MusicSearchViewController searchViewController; + + /** + * Injection point. + */ + @SuppressWarnings("unused") + public static void initialize(Activity parentActivity) { + // Must touch the Music settings to ensure the class is loaded and + // the values can be found when setting the UI preferences. + // Logging anything under non debug ensures this is set. + Logger.printInfo(() -> "Permanent repeat enabled: " + Settings.PERMANENT_REPEAT.get()); + + // YT Music always uses dark mode. + Utils.setIsDarkModeEnabled(true); + + BaseActivityHook.initialize(new MusicActivityHook(), parentActivity); + } + + /** + * Sets the fixed theme for the activity. + */ + @Override + protected void customizeActivityTheme(Activity activity) { + // Override the default YouTube Music theme to increase start padding of list items. + // Custom style located in resources/music/values/style.xml + activity.setTheme(Utils.getResourceIdentifierOrThrow( + "Theme.ReVanced.YouTubeMusic.Settings", "style")); + } + + /** + * Returns the resource ID for the YouTube Music settings layout. + */ + @Override + protected int getContentViewResourceId() { + return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR; + } + + /** + * Returns the fixed background color for the toolbar. + */ + @Override + protected int getToolbarBackgroundColor() { + return Utils.getResourceColor("ytm_color_black"); + } + + /** + * Returns the navigation icon with a color filter applied. + */ + @Override + protected Drawable getNavigationIcon() { + Drawable navigationIcon = MusicPreferenceFragment.getBackButtonDrawable(); + navigationIcon.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); + return navigationIcon; + } + + /** + * Returns the click listener that finishes the activity when the navigation icon is clicked. + */ + @Override + protected View.OnClickListener getNavigationClickListener(Activity activity) { + return view -> { + if (searchViewController != null && searchViewController.isSearchActive()) { + searchViewController.closeSearch(); + } else { + activity.finish(); + } + }; + } + + /** + * Adds search view components to the toolbar for {@link MusicPreferenceFragment}. + * + * @param activity The activity hosting the toolbar. + * @param toolbar The configured toolbar. + * @param fragment The PreferenceFragment associated with the activity. + */ + @Override + protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) { + if (fragment instanceof MusicPreferenceFragment) { + searchViewController = MusicSearchViewController.addSearchViewComponents( + activity, toolbar, (MusicPreferenceFragment) fragment); + } + } + + /** + * Creates a new {@link MusicPreferenceFragment} for the activity. + */ + @Override + protected PreferenceFragment createPreferenceFragment() { + return new MusicPreferenceFragment(); + } + + /** + * Injection point. + *

+ * Overrides {@link Activity#finish()} of the injection Activity. + * + * @return if the original activity finish method should be allowed to run. + */ + @SuppressWarnings("unused") + public static boolean handleFinish() { + return MusicSearchViewController.handleFinish(searchViewController); + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java index 832118829..4feb13d9c 100644 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java @@ -2,7 +2,6 @@ package app.revanced.extension.music.settings; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; - import static app.revanced.extension.shared.settings.Setting.parent; import app.revanced.extension.shared.settings.BaseSettings; diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java new file mode 100644 index 000000000..1ebae16df --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/MusicPreferenceFragment.java @@ -0,0 +1,80 @@ +package app.revanced.extension.music.settings.preference; + +import android.app.Dialog; +import android.preference.PreferenceScreen; +import android.widget.Toolbar; + +import app.revanced.extension.music.settings.MusicActivityHook; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment; + +/** + * Preference fragment for ReVanced settings. + */ +@SuppressWarnings("deprecation") +public class MusicPreferenceFragment extends ToolbarPreferenceFragment { + /** + * The main PreferenceScreen used to display the current set of preferences. + */ + private PreferenceScreen preferenceScreen; + + /** + * Initializes the preference fragment. + */ + @Override + protected void initialize() { + super.initialize(); + + try { + preferenceScreen = getPreferenceScreen(); + Utils.sortPreferenceGroups(preferenceScreen); + setPreferenceScreenToolbar(preferenceScreen); + } catch (Exception ex) { + Logger.printException(() -> "initialize failure", ex); + } + } + + /** + * Called when the fragment starts. + */ + @Override + public void onStart() { + super.onStart(); + try { + // Initialize search controller if needed + if (MusicActivityHook.searchViewController != null) { + // Trigger search data collection after fragment is ready. + MusicActivityHook.searchViewController.initializeSearchData(); + } + } catch (Exception ex) { + Logger.printException(() -> "onStart failure", ex); + } + } + + /** + * Sets toolbar for all nested preference screens. + */ + @Override + protected void customizeToolbar(Toolbar toolbar) { + MusicActivityHook.setToolbarLayoutParams(toolbar); + } + + /** + * Perform actions after toolbar setup. + */ + @Override + protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) { + if (MusicActivityHook.searchViewController != null + && MusicActivityHook.searchViewController.isSearchActive()) { + toolbar.post(() -> MusicActivityHook.searchViewController.closeSearch()); + } + } + + /** + * Returns the preference screen for external access by SearchViewController. + */ + public PreferenceScreen getPreferenceScreenForSearch() { + return preferenceScreen; + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java deleted file mode 100644 index 67ca69ba4..000000000 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java +++ /dev/null @@ -1,38 +0,0 @@ -package app.revanced.extension.music.settings.preference; - -import android.widget.Toolbar; - -import app.revanced.extension.music.settings.GoogleApiActivityHook; -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.Utils; -import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment; - -/** - * Preference fragment for ReVanced settings. - */ -@SuppressWarnings({"deprecation", "NewApi"}) -public class ReVancedPreferenceFragment extends ToolbarPreferenceFragment { - - /** - * Initializes the preference fragment. - */ - @Override - protected void initialize() { - super.initialize(); - - try { - Utils.sortPreferenceGroups(getPreferenceScreen()); - setPreferenceScreenToolbar(getPreferenceScreen()); - } catch (Exception ex) { - Logger.printException(() -> "initialize failure", ex); - } - } - - /** - * Sets toolbar for all nested preference screens. - */ - @Override - protected void customizeToolbar(Toolbar toolbar) { - GoogleApiActivityHook.setToolbarLayoutParams(toolbar); - } -} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchResultsAdapter.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchResultsAdapter.java new file mode 100644 index 000000000..65ccd4ea1 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchResultsAdapter.java @@ -0,0 +1,28 @@ +package app.revanced.extension.music.settings.search; + +import android.content.Context; +import android.preference.PreferenceScreen; + +import app.revanced.extension.shared.settings.search.BaseSearchResultsAdapter; +import app.revanced.extension.shared.settings.search.BaseSearchViewController; +import app.revanced.extension.shared.settings.search.BaseSearchResultItem; + +import java.util.List; + +/** + * Music-specific search results adapter. + */ +@SuppressWarnings("deprecation") +public class MusicSearchResultsAdapter extends BaseSearchResultsAdapter { + + public MusicSearchResultsAdapter(Context context, List items, + BaseSearchViewController.BasePreferenceFragment fragment, + BaseSearchViewController searchViewController) { + super(context, items, fragment, searchViewController); + } + + @Override + protected PreferenceScreen getMainPreferenceScreen() { + return fragment.getPreferenceScreenForSearch(); + } +} diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchViewController.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchViewController.java new file mode 100644 index 000000000..6681a2f02 --- /dev/null +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/search/MusicSearchViewController.java @@ -0,0 +1,71 @@ +package app.revanced.extension.music.settings.search; + +import android.app.Activity; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.view.View; +import android.widget.Toolbar; + +import app.revanced.extension.music.settings.preference.MusicPreferenceFragment; +import app.revanced.extension.shared.settings.search.*; + +/** + * Music-specific search view controller implementation. + */ +@SuppressWarnings("deprecation") +public class MusicSearchViewController extends BaseSearchViewController { + + public static MusicSearchViewController addSearchViewComponents(Activity activity, Toolbar toolbar, + MusicPreferenceFragment fragment) { + return new MusicSearchViewController(activity, toolbar, fragment); + } + + private MusicSearchViewController(Activity activity, Toolbar toolbar, MusicPreferenceFragment fragment) { + super(activity, toolbar, new PreferenceFragmentAdapter(fragment)); + } + + @Override + protected BaseSearchResultsAdapter createSearchResultsAdapter() { + return new MusicSearchResultsAdapter(activity, filteredSearchItems, fragment, this); + } + + @Override + protected boolean isSpecialPreferenceGroup(Preference preference) { + // Music doesn't have SponsorBlock, so no special groups. + return false; + } + + @Override + protected void setupSpecialPreferenceListeners(BaseSearchResultItem item) { + // Music doesn't have special preferences. + // This method can be empty or handle music-specific preferences if any. + } + + // Static method for handling Activity finish + public static boolean handleFinish(MusicSearchViewController searchViewController) { + if (searchViewController != null && searchViewController.isSearchActive()) { + searchViewController.closeSearch(); + return true; + } + return false; + } + + // Adapter to wrap MusicPreferenceFragment to BasePreferenceFragment interface. + private record PreferenceFragmentAdapter(MusicPreferenceFragment fragment) implements BasePreferenceFragment { + + @Override + public PreferenceScreen getPreferenceScreenForSearch() { + return fragment.getPreferenceScreenForSearch(); + } + + @Override + public View getView() { + return fragment.getView(); + } + + @Override + public Activity getActivity() { + return fragment.getActivity(); + } + } +} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java index 3f9f0af11..978ee7131 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java @@ -27,6 +27,7 @@ import java.util.Locale; import app.revanced.extension.shared.requests.Requester; import app.revanced.extension.shared.requests.Route; +import app.revanced.extension.shared.ui.CustomDialog; @SuppressWarnings("unused") public class GmsCoreSupport { @@ -80,17 +81,17 @@ public class GmsCoreSupport { // Otherwise, if device is in dark mode the dialog is shown with wrong color scheme. Utils.runOnMainThreadDelayed(() -> { // Create the custom dialog. - Pair dialogPair = Utils.createCustomDialog( + Pair dialogPair = CustomDialog.create( context, str("gms_core_dialog_title"), // Title. - str(dialogMessageRef), // Message. - null, // No EditText. - str(positiveButtonTextRef), // OK button text. + str(dialogMessageRef), // Message. + null, // No EditText. + str(positiveButtonTextRef), // OK button text. () -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable. - null, // No Cancel button action. - null, // No Neutral button text. - null, // No Neutral button action. - true // Dismiss dialog when onNeutralClick. + null, // No Cancel button action. + null, // No Neutral button text. + null, // No Neutral button action. + true // Dismiss dialog when onNeutralClick. ); Dialog dialog = dialogPair.first; diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index 6fbdc233a..feeb379a7 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -4,6 +4,8 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; import android.app.DialogFragment; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -12,9 +14,6 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; -import android.graphics.Typeface; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RoundRectShape; import android.net.ConnectivityManager; import android.os.Build; import android.os.Bundle; @@ -23,9 +22,6 @@ import android.os.Looper; import android.preference.Preference; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; import android.util.DisplayMetrics; import android.util.Pair; import android.util.TypedValue; @@ -37,13 +33,9 @@ import android.view.Window; import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.widget.Button; -import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.RelativeLayout; -import android.widget.ScrollView; -import android.widget.TextView; import android.widget.Toast; import android.widget.Toolbar; @@ -69,6 +61,7 @@ import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference; +@SuppressWarnings("NewApi") public class Utils { @SuppressLint("StaticFieldLeak") @@ -278,41 +271,63 @@ public class Utils { * @return zero, if the resource is not found. */ @SuppressLint("DiscouragedApi") - public static int getResourceIdentifier(Context context, String resourceIdentifierName, String type) { + public static int getResourceIdentifier(Context context, String resourceIdentifierName, @Nullable String type) { return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName()); } + public static int getResourceIdentifierOrThrow(Context context, String resourceIdentifierName, @Nullable String type) { + final int resourceId = getResourceIdentifier(context, resourceIdentifierName, type); + if (resourceId == 0) { + throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName + + " type: " + type); + } + return resourceId; + } + /** * @return zero, if the resource is not found. + * @see #getResourceIdentifierOrThrow(String, String) */ - public static int getResourceIdentifier(String resourceIdentifierName, String type) { + public static int getResourceIdentifier(String resourceIdentifierName, @Nullable String type) { return getResourceIdentifier(getContext(), resourceIdentifierName, type); } + /** + * @return The resource identifier, or throws an exception if not found. + */ + public static int getResourceIdentifierOrThrow(String resourceIdentifierName, @Nullable String type) { + final int resourceId = getResourceIdentifier(getContext(), resourceIdentifierName, type); + if (resourceId == 0) { + throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName + + " type: " + type); + } + return resourceId; + } + public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException { - return getContext().getResources().getInteger(getResourceIdentifier(resourceIdentifierName, "integer")); + return getContext().getResources().getInteger(getResourceIdentifierOrThrow(resourceIdentifierName, "integer")); } public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException { - return AnimationUtils.loadAnimation(getContext(), getResourceIdentifier(resourceIdentifierName, "anim")); + return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(resourceIdentifierName, "anim")); } @ColorInt public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException { //noinspection deprecation - return getContext().getResources().getColor(getResourceIdentifier(resourceIdentifierName, "color")); + return getContext().getResources().getColor(getResourceIdentifierOrThrow(resourceIdentifierName, "color")); } public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException { - return getContext().getResources().getDimensionPixelSize(getResourceIdentifier(resourceIdentifierName, "dimen")); + return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen")); } public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException { - return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen")); + return getContext().getResources().getDimension(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen")); } public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException { - return getContext().getResources().getStringArray(getResourceIdentifier(resourceIdentifierName, "array")); + return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(resourceIdentifierName, "array")); } public interface MatchFilter { @@ -323,13 +338,9 @@ public class Utils { * Includes sub children. */ public static R getChildViewByResourceName(View view, String str) { - var child = view.findViewById(Utils.getResourceIdentifier(str, "id")); - if (child != null) { - //noinspection unchecked - return (R) child; - } - - throw new IllegalArgumentException("View with resource name not found: " + str); + var child = view.findViewById(Utils.getResourceIdentifierOrThrow(str, "id")); + //noinspection unchecked + return (R) child; } /** @@ -415,9 +426,9 @@ public class Utils { } public static void setClipboard(CharSequence text) { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context + ClipboardManager clipboard = (ClipboardManager) context .getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text); + ClipData clip = ClipData.newPlainText("ReVanced", text); clipboard.setPrimaryClip(clip); } @@ -577,7 +588,13 @@ public class Utils { showToast(messageToToast, Toast.LENGTH_LONG); } - private static void showToast(String messageToToast, int toastDuration) { + /** + * Safe to call from any thread. + * + * @param messageToToast Message to show. + * @param toastDuration Either {@link Toast#LENGTH_SHORT} or {@link Toast#LENGTH_LONG}. + */ + public static void showToast(String messageToToast, int toastDuration) { Objects.requireNonNull(messageToToast); runOnMainThreadNowOrLater(() -> { Context currentContext = context; @@ -747,396 +764,32 @@ public class Utils { } /** - * Creates a custom dialog with a styled layout, including a title, message, buttons, and an - * optional EditText. The dialog's appearance adapts to the app's dark mode setting, with - * rounded corners and customizable button actions. Buttons adjust dynamically to their text - * content and are arranged in a single row if they fit within 80% of the screen width, - * with the Neutral button aligned to the left and OK/Cancel buttons centered on the right. - * If buttons do not fit, each is placed on a separate row, all aligned to the right. + * Configures the parameters of a dialog window, including its width, gravity, vertical offset and background dimming. + * The width is calculated as a percentage of the screen's portrait width and the vertical offset is specified in DIP. + * The default dialog background is removed to allow for custom styling. * - * @param context Context used to create the dialog. - * @param title Title text of the dialog. - * @param message Message text of the dialog (supports Spanned for HTML), or null if replaced by EditText. - * @param editText EditText to include in the dialog, or null if no EditText is needed. - * @param okButtonText OK button text, or null to use the default "OK" string. - * @param onOkClick Action to perform when the OK button is clicked. - * @param onCancelClick Action to perform when the Cancel button is clicked, or null if no Cancel button is needed. - * @param neutralButtonText Neutral button text, or null if no Neutral button is needed. - * @param onNeutralClick Action to perform when the Neutral button is clicked, or null if no Neutral button is needed. - * @param dismissDialogOnNeutralClick If the dialog should be dismissed when the Neutral button is clicked. - * @return The Dialog and its main LinearLayout container. + * @param window The {@link Window} object to configure. + * @param gravity The gravity for positioning the dialog (e.g., {@link Gravity#BOTTOM}). + * @param yOffsetDip The vertical offset from the gravity position in DIP. + * @param widthPercentage The width of the dialog as a percentage of the screen's portrait width (0-100). + * @param dimAmount If true, sets the background dim amount to 0 (no dimming); if false, leaves the default dim amount. */ - @SuppressWarnings("ExtractMethodRecommender") - public static Pair createCustomDialog( - Context context, String title, CharSequence message, @Nullable EditText editText, - String okButtonText, Runnable onOkClick, Runnable onCancelClick, - @Nullable String neutralButtonText, @Nullable Runnable onNeutralClick, - boolean dismissDialogOnNeutralClick - ) { - Logger.printDebug(() -> "Creating custom dialog with title: " + title); - - Dialog dialog = new Dialog(context); - dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar. - - // Preset size constants. - final int dip4 = dipToPixels(4); - final int dip8 = dipToPixels(8); - final int dip16 = dipToPixels(16); - final int dip24 = dipToPixels(24); - - // Create main layout. - LinearLayout mainLayout = new LinearLayout(context); - mainLayout.setOrientation(LinearLayout.VERTICAL); - mainLayout.setPadding(dip24, dip16, dip24, dip24); - // Set rounded rectangle background. - ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape( - createCornerRadii(28), null, null)); - mainBackground.getPaint().setColor(getDialogBackgroundColor()); // Dialog background. - mainLayout.setBackground(mainBackground); - - // Title. - if (!TextUtils.isEmpty(title)) { - TextView titleView = new TextView(context); - titleView.setText(title); - titleView.setTypeface(Typeface.DEFAULT_BOLD); - titleView.setTextSize(18); - titleView.setTextColor(getAppForegroundColor()); - titleView.setGravity(Gravity.CENTER); - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ); - layoutParams.setMargins(0, 0, 0, dip16); - titleView.setLayoutParams(layoutParams); - mainLayout.addView(titleView); - } - - // Create content container (message/EditText) inside a ScrollView only if message or editText is provided. - ScrollView contentScrollView = null; - LinearLayout contentContainer; - if (message != null || editText != null) { - contentScrollView = new ScrollView(context); - contentScrollView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar. - contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); - if (editText != null) { - ShapeDrawable scrollViewBackground = new ShapeDrawable(new RoundRectShape( - createCornerRadii(10), null, null)); - scrollViewBackground.getPaint().setColor(getEditTextBackground()); - contentScrollView.setPadding(dip8, dip8, dip8, dip8); - contentScrollView.setBackground(scrollViewBackground); - contentScrollView.setClipToOutline(true); - } - LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - 0, - 1.0f // Weight to take available space. - ); - contentScrollView.setLayoutParams(contentParams); - contentContainer = new LinearLayout(context); - contentContainer.setOrientation(LinearLayout.VERTICAL); - contentScrollView.addView(contentContainer); - - // Message (if not replaced by EditText). - if (editText == null) { - TextView messageView = new TextView(context); - messageView.setText(message); // Supports Spanned (HTML). - messageView.setTextSize(16); - messageView.setTextColor(getAppForegroundColor()); - // Enable HTML link clicking if the message contains links. - if (message instanceof Spanned) { - messageView.setMovementMethod(LinkMovementMethod.getInstance()); - } - LinearLayout.LayoutParams messageParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ); - messageView.setLayoutParams(messageParams); - contentContainer.addView(messageView); - } - - // EditText (if provided). - if (editText != null) { - // Remove EditText from its current parent, if any. - ViewGroup parent = (ViewGroup) editText.getParent(); - if (parent != null) { - parent.removeView(editText); - } - // Style the EditText to match the dialog theme. - editText.setTextColor(getAppForegroundColor()); - editText.setBackgroundColor(Color.TRANSPARENT); - editText.setPadding(0, 0, 0, 0); - LinearLayout.LayoutParams editTextParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - contentContainer.addView(editText, editTextParams); - } - } - - // Button container. - LinearLayout buttonContainer = new LinearLayout(context); - buttonContainer.setOrientation(LinearLayout.VERTICAL); - buttonContainer.removeAllViews(); - LinearLayout.LayoutParams buttonContainerParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - buttonContainerParams.setMargins(0, dip16, 0, 0); - buttonContainer.setLayoutParams(buttonContainerParams); - - // Lists to track buttons. - List