From 67673d089f83361290f1a9cc54c17690695cd5e1 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 2 Mar 2024 08:29:38 +0100 Subject: [PATCH 01/40] build: Bump dependencies --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5bd1fbd4..d2b86313 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.2.2" +agp = "8.3.0" annotation = "1.7.1" kotlin = "1.9.22" appcompat = "1.7.0-alpha03" From 5d14f53acd0b1eabd6951543edd7d7c662b6c502 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 3 Mar 2024 00:04:38 +0100 Subject: [PATCH 02/40] fix(YouTube - Announcements): Only compare ID to not show same announcement after a fix-up (#579) Previously, the announcement content was hashed and compared with the locally saved hash. Sometimes announcements are fixed up, altering the hash, so instead, compare the ID. --- .../announcements/AnnouncementsPatch.java | 54 +++++++++++-------- .../youtube/settings/Settings.java | 31 ++++------- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/AnnouncementsPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/AnnouncementsPatch.java index 6f2c9e50..8de878a7 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/AnnouncementsPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/AnnouncementsPatch.java @@ -6,12 +6,11 @@ import android.text.Html; import android.text.method.LinkMovementMethod; import android.widget.TextView; import androidx.annotation.RequiresApi; - +import app.revanced.integrations.shared.Logger; +import app.revanced.integrations.shared.Utils; import app.revanced.integrations.youtube.patches.announcements.requests.AnnouncementsRoutes; import app.revanced.integrations.youtube.requests.Requester; import app.revanced.integrations.youtube.settings.Settings; -import app.revanced.integrations.shared.Logger; -import app.revanced.integrations.shared.Utils; import org.json.JSONObject; import java.io.IOException; @@ -49,9 +48,10 @@ public final class AnnouncementsPatch { try { // Do not show the announcement if the request failed. if (connection.getResponseCode() != 200) { - if (Settings.ANNOUNCEMENT_LAST_HASH.get().isEmpty()) return; + if (Settings.ANNOUNCEMENT_LAST_ID.isSetToDefault()) + return; - Settings.ANNOUNCEMENT_LAST_HASH.resetToDefault(); + Settings.ANNOUNCEMENT_LAST_ID.resetToDefault(); Utils.showToastLong(str("revanced_announcements_connection_failed")); return; @@ -65,22 +65,20 @@ public final class AnnouncementsPatch { var jsonString = Requester.parseInputStreamAndClose(connection.getInputStream(), false); - // Do not show the announcement if it is older or the same as the last one. - final byte[] hashBytes = MessageDigest.getInstance("SHA-256").digest(jsonString.getBytes(StandardCharsets.UTF_8)); - final var hash = java.util.Base64.getEncoder().encodeToString(hashBytes); - if (hash.equals(Settings.ANNOUNCEMENT_LAST_HASH.get())) return; // Parse the announcement. Fall-back to raw string if it fails. + int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue; String title; String message; Level level = Level.INFO; try { final var announcement = new JSONObject(jsonString); + id = announcement.getInt("id"); title = announcement.getString("title"); message = announcement.getJSONObject("content").getString("message"); - if (!announcement.isNull("level")) level = Level.fromInt(announcement.getInt("level")); + } catch (Throwable ex) { Logger.printException(() -> "Failed to parse announcement. Fall-backing to raw string", ex); @@ -88,6 +86,28 @@ public final class AnnouncementsPatch { message = jsonString; } + // TODO: Remove this migration code after a few months. + if (!Settings.DEPRECATED_ANNOUNCEMENT_LAST_HASH.isSetToDefault()){ + final byte[] hashBytes = MessageDigest + .getInstance("SHA-256") + .digest(jsonString.getBytes(StandardCharsets.UTF_8)); + + final var hash = java.util.Base64.getEncoder().encodeToString(hashBytes); + + // Migrate to saving the id instead of the hash. + if (hash.equals(Settings.DEPRECATED_ANNOUNCEMENT_LAST_HASH.get())) { + Settings.ANNOUNCEMENT_LAST_ID.save(id); + } + + Settings.DEPRECATED_ANNOUNCEMENT_LAST_HASH.resetToDefault(); + } + + // Do not show the announcement, if the last announcement id is the same as the current one. + if (Settings.ANNOUNCEMENT_LAST_ID.get() == id) return; + + + + int finalId = id; final var finalTitle = title; final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT); final Level finalLevel = level; @@ -99,7 +119,7 @@ public final class AnnouncementsPatch { .setMessage(finalMessage) .setIcon(finalLevel.icon) .setPositiveButton("Ok", (dialog, which) -> { - Settings.ANNOUNCEMENT_LAST_HASH.save(hash); + Settings.ANNOUNCEMENT_LAST_ID.save(finalId); dialog.dismiss(); }).setNegativeButton("Dismiss", (dialog, which) -> { dialog.dismiss(); @@ -119,18 +139,6 @@ public final class AnnouncementsPatch { }); } - /** - * Clears the last announcement hash if it is not empty. - * - * @return true if the last announcement hash was empty. - */ - private static boolean emptyLastAnnouncementHash() { - if (Settings.ANNOUNCEMENT_LAST_HASH.get().isEmpty()) return true; - Settings.ANNOUNCEMENT_LAST_HASH.resetToDefault(); - - return false; - } - private static String getOrSetConsumer() { final var consumer = Settings.ANNOUNCEMENT_CONSUMER.get(); if (!consumer.isEmpty()) return consumer; diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index e011f941..7967f6a7 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -1,29 +1,17 @@ package app.revanced.integrations.youtube.settings; -import static java.lang.Boolean.FALSE; -import static java.lang.Boolean.TRUE; -import static app.revanced.integrations.shared.settings.Setting.migrateFromOldPreferences; -import static app.revanced.integrations.shared.settings.Setting.migrateOldSettingToNew; -import static app.revanced.integrations.shared.settings.Setting.parent; -import static app.revanced.integrations.shared.settings.Setting.parentsAny; -import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE; -import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP; -import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY; -import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE; +import app.revanced.integrations.shared.settings.*; +import app.revanced.integrations.shared.settings.preference.SharedPrefCategory; +import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings; import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import app.revanced.integrations.shared.settings.BaseSettings; -import app.revanced.integrations.shared.settings.BooleanSetting; -import app.revanced.integrations.shared.settings.FloatSetting; -import app.revanced.integrations.shared.settings.IntegerSetting; -import app.revanced.integrations.shared.settings.LongSetting; -import app.revanced.integrations.shared.settings.Setting; -import app.revanced.integrations.shared.settings.StringSetting; -import app.revanced.integrations.shared.settings.preference.SharedPrefCategory; -import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings; +import static app.revanced.integrations.shared.settings.Setting.*; +import static app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour.*; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; public class Settings extends BaseSettings { // External downloader @@ -205,7 +193,9 @@ public class Settings extends BaseSettings { public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE); public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE); public static final StringSetting ANNOUNCEMENT_CONSUMER = new StringSetting("revanced_announcement_consumer", "", false, false); - public static final StringSetting ANNOUNCEMENT_LAST_HASH = new StringSetting("revanced_announcement_last_hash", ""); + @Deprecated + public static final StringSetting DEPRECATED_ANNOUNCEMENT_LAST_HASH = new StringSetting("revanced_announcement_last_hash", ""); + public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1); public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE); public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG= new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE, "revanced_remove_viewer_discretion_dialog_user_dialog_message"); @@ -245,6 +235,7 @@ public class Settings extends BaseSettings { * Do not use directly, instead use {@link SponsorBlockSettings} */ public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", ""); + @Deprecated public static final StringSetting DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING = new StringSetting("uuid", ""); // Delete sometime in 2024 public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED)); public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED)); From e5aa30ebe578816d45fc9e82f11de5c6f7a7746d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 2 Mar 2024 23:07:44 +0000 Subject: [PATCH 03/40] chore(release): 1.4.1-dev.1 [skip ci] ## [1.4.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.4.0...v1.4.1-dev.1) (2024-03-02) ### Bug Fixes * **YouTube - Announcements:** Only compare ID to not show same announcement after a fix-up ([#579](https://github.com/ReVanced/revanced-integrations/issues/579)) ([5d14f53](https://github.com/ReVanced/revanced-integrations/commit/5d14f53acd0b1eabd6951543edd7d7c662b6c502)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1b22601..17799b23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.4.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.4.0...v1.4.1-dev.1) (2024-03-02) + + +### Bug Fixes + +* **YouTube - Announcements:** Only compare ID to not show same announcement after a fix-up ([#579](https://github.com/ReVanced/revanced-integrations/issues/579)) ([5d14f53](https://github.com/ReVanced/revanced-integrations/commit/5d14f53acd0b1eabd6951543edd7d7c662b6c502)) + # [1.4.0](https://github.com/ReVanced/revanced-integrations/compare/v1.3.2...v1.4.0) (2024-03-02) diff --git a/gradle.properties b/gradle.properties index 5247c613..248aadf6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.4.0 +version = 1.4.1-dev.1 From 36aa6c9aa88495a79f64e0ca221694f4d18596a5 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 3 Mar 2024 00:15:00 +0100 Subject: [PATCH 04/40] chore: Lint code --- .editorconfig | 3 ++ .../twitter/patches/hook/json/BaseJsonHook.kt | 2 +- .../twitter/patches/hook/json/JsonHook.kt | 2 +- .../patches/hook/json/JsonHookPatch.kt | 2 +- .../twitter/patches/hook/patch/Hook.kt | 2 +- .../twitter/patches/hook/patch/ads/AdsHook.kt | 3 +- .../patches/hook/patch/dummy/DummyHook.kt | 2 +- .../recommendation/RecommendedUsersHook.kt | 3 +- .../twitter/utils/json/JsonUtils.kt | 2 +- .../twitter/utils/stream/StreamUtils.kt | 2 +- .../revanced/integrations/youtube/Event.kt | 1 - .../PlayerControlsVisibilityObserver.kt | 4 +- .../youtube/shared/PlayerOverlays.kt | 22 +++++------ .../integrations/youtube/shared/PlayerType.kt | 15 ++++--- .../integrations/youtube/shared/VideoState.kt | 9 +++-- .../SwipeControlsConfigurationProvider.kt | 4 +- .../SwipeControlsHostActivity.kt | 39 ++++++++++++------- .../controller/AudioVolumeController.kt | 14 ++++--- .../controller/ScreenBrightnessController.kt | 4 +- .../controller/SwipeZonesController.kt | 16 ++++---- .../controller/VolumeKeysController.kt | 4 +- .../gesture/ClassicSwipeController.kt | 18 ++++++--- .../gesture/PressToSwipeController.kt | 18 ++++++--- .../gesture/core/BaseGestureController.kt | 16 ++++---- .../controller/gesture/core/SwipeDetector.kt | 8 ++-- .../core/VolumeAndBrightnessScroller.kt | 10 ++--- .../youtube/swipecontrols/misc/Point.kt | 2 +- .../youtube/swipecontrols/misc/Rectangle.kt | 3 +- .../misc/ScrollDistanceHelper.kt | 6 +-- .../misc/SwipeControlsOverlay.kt | 2 +- .../swipecontrols/misc/SwipeControlsUtils.kt | 3 +- .../views/SwipeControlsOverlayLayout.kt | 20 +++++----- stub/build.gradle.kts | 2 +- 33 files changed, 150 insertions(+), 113 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..2d6d258f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.{kt,kts}] +ktlint_code_style = intellij_idea +ktlint_standard_no-wildcard-imports = disabled \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/BaseJsonHook.kt b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/BaseJsonHook.kt index 536a43e1..18d5019e 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/BaseJsonHook.kt +++ b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/BaseJsonHook.kt @@ -6,4 +6,4 @@ abstract class BaseJsonHook : JsonHook { abstract fun apply(json: JSONObject) override fun transform(json: JSONObject) = json.apply { apply(json) } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/JsonHook.kt b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/JsonHook.kt index 3edfca4e..42751e87 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/JsonHook.kt +++ b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/JsonHook.kt @@ -12,4 +12,4 @@ interface JsonHook : Hook { fun transform(json: JSONObject): JSONObject override fun hook(type: JSONObject) = transform(type) -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/JsonHookPatch.kt b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/JsonHookPatch.kt index 6e4067b9..1eb96cfe 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/JsonHookPatch.kt +++ b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/json/JsonHookPatch.kt @@ -27,4 +27,4 @@ object JsonHookPatch { return StreamUtils.fromString(jsonObject.toString()) } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/Hook.kt b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/Hook.kt index d6d163ef..d85bca3e 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/Hook.kt +++ b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/Hook.kt @@ -6,4 +6,4 @@ interface Hook { * @param type The type to hook */ fun hook(type: T): T -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/ads/AdsHook.kt b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/ads/AdsHook.kt index 7c33a5b2..8411f97c 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/ads/AdsHook.kt +++ b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/ads/AdsHook.kt @@ -4,7 +4,6 @@ import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker import org.json.JSONObject - object AdsHook : BaseJsonHook() { /** * Strips JSONObject from promoted ads. @@ -12,4 +11,4 @@ object AdsHook : BaseJsonHook() { * @param json The JSONObject. */ override fun apply(json: JSONObject) = TwiFucker.hidePromotedAds(json) -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/dummy/DummyHook.kt b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/dummy/DummyHook.kt index 1a4f583c..3e1bbaae 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/dummy/DummyHook.kt +++ b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/dummy/DummyHook.kt @@ -11,4 +11,4 @@ object DummyHook : BaseJsonHook() { override fun apply(json: JSONObject) { // Do nothing. } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/recommendation/RecommendedUsersHook.kt b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/recommendation/RecommendedUsersHook.kt index dfbb1ffb..21b19e7f 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/recommendation/RecommendedUsersHook.kt +++ b/app/src/main/java/app/revanced/integrations/twitter/patches/hook/patch/recommendation/RecommendedUsersHook.kt @@ -4,7 +4,6 @@ import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker import org.json.JSONObject - object RecommendedUsersHook : BaseJsonHook() { /** * Strips JSONObject from recommended users. @@ -12,4 +11,4 @@ object RecommendedUsersHook : BaseJsonHook() { * @param json The JSONObject. */ override fun apply(json: JSONObject) = TwiFucker.hideRecommendedUsers(json) -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/twitter/utils/json/JsonUtils.kt b/app/src/main/java/app/revanced/integrations/twitter/utils/json/JsonUtils.kt index 4b7a5002..80675c80 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/utils/json/JsonUtils.kt +++ b/app/src/main/java/app/revanced/integrations/twitter/utils/json/JsonUtils.kt @@ -10,4 +10,4 @@ object JsonUtils { @JvmStatic @Throws(IOException::class, JSONException::class) fun parseJson(jsonInputStream: InputStream) = JSONObject(StreamUtils.toString(jsonInputStream)) -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/twitter/utils/stream/StreamUtils.kt b/app/src/main/java/app/revanced/integrations/twitter/utils/stream/StreamUtils.kt index 7e008eb8..b7551ebe 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/utils/stream/StreamUtils.kt +++ b/app/src/main/java/app/revanced/integrations/twitter/utils/stream/StreamUtils.kt @@ -21,4 +21,4 @@ object StreamUtils { fun fromString(string: String): InputStream { return ByteArrayInputStream(string.toByteArray()) } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/Event.kt b/app/src/main/java/app/revanced/integrations/youtube/Event.kt index 7b25af7f..63e79578 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/Event.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/Event.kt @@ -27,4 +27,3 @@ class Event { observer.invoke(value) } } - diff --git a/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerControlsVisibilityObserver.kt b/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerControlsVisibilityObserver.kt index 912f369e..c31e9d4c 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerControlsVisibilityObserver.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerControlsVisibilityObserver.kt @@ -12,7 +12,7 @@ import java.lang.ref.WeakReference * @param activity activity that contains the controls_layout view */ class PlayerControlsVisibilityObserverImpl( - private val activity: Activity + private val activity: Activity, ) : PlayerControlsVisibilityObserver { /** @@ -81,4 +81,4 @@ interface PlayerControlsVisibilityObserver { * is the value of [playerControlsVisibility] equal to [View.VISIBLE]? */ val arePlayerControlsVisible: Boolean -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerOverlays.kt b/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerOverlays.kt index 3b15f043..9bce2bc4 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerOverlays.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerOverlays.kt @@ -2,8 +2,8 @@ package app.revanced.integrations.youtube.shared import android.view.View import android.view.ViewGroup -import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle import app.revanced.integrations.youtube.Event +import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle /** * hooking class for player overlays @@ -42,8 +42,8 @@ object PlayerOverlays { ChildrenChangeEventArgs( parent, child, - false - ) + false, + ), ) } } @@ -54,8 +54,8 @@ object PlayerOverlays { ChildrenChangeEventArgs( parent, child, - true - ) + true, + ), ) } } @@ -69,15 +69,15 @@ object PlayerOverlays { oldLeft, oldTop, oldRight - oldLeft, - oldBottom - oldTop + oldBottom - oldTop, ), Rectangle( newLeft, newTop, newRight - newLeft, - newBottom - newTop - ) - ) + newBottom - newTop, + ), + ), ) } } @@ -87,11 +87,11 @@ object PlayerOverlays { data class ChildrenChangeEventArgs( val overlaysLayout: ViewGroup, val childView: View, - val wasChildRemoved: Boolean + val wasChildRemoved: Boolean, ) data class LayoutChangeEventArgs( val overlaysLayout: ViewGroup, val oldRect: Rectangle, - val newRect: Rectangle + val newRect: Rectangle, ) diff --git a/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerType.kt b/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerType.kt index 64634ceb..15d1496d 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerType.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerType.kt @@ -1,8 +1,8 @@ package app.revanced.integrations.youtube.shared -import app.revanced.integrations.youtube.patches.VideoInformation -import app.revanced.integrations.youtube.Event import app.revanced.integrations.shared.Logger +import app.revanced.integrations.youtube.Event +import app.revanced.integrations.youtube.patches.VideoInformation /** * Main player type. @@ -12,11 +12,13 @@ enum class PlayerType { * Either no video, or a Short is playing. */ NONE, + /** * A Short is playing. Occurs if a regular video is first opened * and then a Short is opened (without first closing the regular video). */ HIDDEN, + /** * A regular video is minimized. * @@ -28,6 +30,7 @@ enum class PlayerType { WATCH_WHILE_FULLSCREEN, WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN, WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED, + /** * Player is either sliding to [HIDDEN] state because a Short was opened while a regular video is on screen. * OR @@ -35,12 +38,14 @@ enum class PlayerType { */ WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED, WATCH_WHILE_SLIDING_FULLSCREEN_DISMISSED, + /** * Home feed video playback. */ INLINE_MINIMAL, VIRTUAL_REALITY_FULLSCREEN, - WATCH_WHILE_PICTURE_IN_PICTURE; + WATCH_WHILE_PICTURE_IN_PICTURE, + ; companion object { @@ -67,6 +72,7 @@ enum class PlayerType { currentPlayerType = value onChange(currentPlayerType) } + @Volatile // value is read/write from different threads private var currentPlayerType = NONE @@ -126,5 +132,4 @@ enum class PlayerType { fun isNoneHiddenOrMinimized(): Boolean { return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED } - -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/shared/VideoState.kt b/app/src/main/java/app/revanced/integrations/youtube/shared/VideoState.kt index dfae4133..75db347b 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/shared/VideoState.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/shared/VideoState.kt @@ -12,10 +12,13 @@ enum class VideoState { PAUSED, RECOVERABLE_ERROR, UNRECOVERABLE_ERROR, + /** * @see [VideoInformation.isAtEndOfVideo] */ - ENDED; + ENDED, + + ; companion object { @@ -43,6 +46,6 @@ enum class VideoState { currentVideoState = value } - private var currentVideoState : VideoState? = null + private var currentVideoState: VideoState? = null } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/SwipeControlsConfigurationProvider.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/SwipeControlsConfigurationProvider.kt index e916186b..0e2d7997 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/SwipeControlsConfigurationProvider.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/SwipeControlsConfigurationProvider.kt @@ -11,7 +11,7 @@ import app.revanced.integrations.youtube.shared.PlayerType * @param context the context to create in */ class SwipeControlsConfigurationProvider( - private val context: Context + private val context: Context, ) { //region swipe enable /** @@ -105,4 +105,4 @@ class SwipeControlsConfigurationProvider( get() = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get() //endregion -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/SwipeControlsHostActivity.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/SwipeControlsHostActivity.kt index 838f6ecb..e6f233e8 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/SwipeControlsHostActivity.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/SwipeControlsHostActivity.kt @@ -6,6 +6,8 @@ import android.os.Bundle import android.view.KeyEvent import android.view.MotionEvent import android.view.ViewGroup +import app.revanced.integrations.shared.Logger.printDebug +import app.revanced.integrations.shared.Logger.printException import app.revanced.integrations.youtube.shared.PlayerType import app.revanced.integrations.youtube.swipecontrols.controller.AudioVolumeController import app.revanced.integrations.youtube.swipecontrols.controller.ScreenBrightnessController @@ -16,8 +18,6 @@ import app.revanced.integrations.youtube.swipecontrols.controller.gesture.PressT import app.revanced.integrations.youtube.swipecontrols.controller.gesture.core.GestureController import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle import app.revanced.integrations.youtube.swipecontrols.views.SwipeControlsOverlayLayout -import app.revanced.integrations.shared.Logger.printDebug -import app.revanced.integrations.shared.Logger.printException import java.lang.ref.WeakReference /** @@ -80,14 +80,18 @@ class SwipeControlsHostActivity : Activity() { override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { ensureInitialized() - return if ((ev != null) && gesture.submitTouchEvent(ev)) true else { + return if ((ev != null) && gesture.submitTouchEvent(ev)) { + true + } else { super.dispatchTouchEvent(ev) } } override fun dispatchKeyEvent(ev: KeyEvent?): Boolean { ensureInitialized() - return if ((ev != null) && keys.onKeyEvent(ev)) true else { + return if ((ev != null) && keys.onKeyEvent(ev)) { + true + } else { super.dispatchKeyEvent(ev) } } @@ -139,7 +143,7 @@ class SwipeControlsHostActivity : Activity() { contentRoot.x.toInt(), contentRoot.y.toInt(), contentRoot.width, - contentRoot.height + contentRoot.height, ) } @@ -157,7 +161,7 @@ class SwipeControlsHostActivity : Activity() { * (re) attaches swipe overlays */ private fun reAttachOverlays() { - printDebug{ "attaching swipe controls overlay" } + printDebug { "attaching swipe controls overlay" } contentRoot.removeView(overlay) contentRoot.addView(overlay) } @@ -168,7 +172,7 @@ class SwipeControlsHostActivity : Activity() { * @param type the new player type */ private fun onPlayerTypeChanged(type: PlayerType) { - if (config.shouldSaveAndRestoreBrightness) + if (config.shouldSaveAndRestoreBrightness) { when (type) { PlayerType.WATCH_WHILE_FULLSCREEN -> screen?.restore() else -> { @@ -176,29 +180,38 @@ class SwipeControlsHostActivity : Activity() { screen?.restoreDefaultBrightness() } } + } } /** * create the audio volume controller */ private fun createAudioController() = - if (config.enableVolumeControls) - AudioVolumeController(this) else null + if (config.enableVolumeControls) { + AudioVolumeController(this) + } else { + null + } /** * create the screen brightness controller instance */ private fun createScreenController() = - if (config.enableBrightnessControl) - ScreenBrightnessController(this) else null + if (config.enableBrightnessControl) { + ScreenBrightnessController(this) + } else { + null + } /** * create the gesture controller based on settings */ private fun createGestureController() = - if (config.shouldEnablePressToSwipe) + if (config.shouldEnablePressToSwipe) { PressToSwipeController(this) - else ClassicSwipeController(this) + } else { + ClassicSwipeController(this) + } companion object { /** diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/AudioVolumeController.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/AudioVolumeController.kt index ef4bbefb..f607c1fa 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/AudioVolumeController.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/AudioVolumeController.kt @@ -3,8 +3,8 @@ package app.revanced.integrations.youtube.swipecontrols.controller import android.content.Context import android.media.AudioManager import android.os.Build -import app.revanced.integrations.youtube.swipecontrols.misc.clamp import app.revanced.integrations.shared.Logger.printException +import app.revanced.integrations.youtube.swipecontrols.misc.clamp import kotlin.properties.Delegates /** @@ -15,7 +15,7 @@ import kotlin.properties.Delegates */ class AudioVolumeController( context: Context, - private val targetStream: Int = AudioManager.STREAM_MUSIC + private val targetStream: Int = AudioManager.STREAM_MUSIC, ) { /** @@ -34,9 +34,13 @@ class AudioVolumeController( audioManager = mgr maximumVolumeIndex = audioManager.getStreamMaxVolume(targetStream) minimumVolumeIndex = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) audioManager.getStreamMinVolume( - targetStream - ) else 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + audioManager.getStreamMinVolume( + targetStream, + ) + } else { + 0 + } } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/ScreenBrightnessController.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/ScreenBrightnessController.kt index 356d9813..1cf282e8 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/ScreenBrightnessController.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/ScreenBrightnessController.kt @@ -10,7 +10,7 @@ import app.revanced.integrations.youtube.swipecontrols.misc.clamp * @param host the host activity of which the brightness is adjusted */ class ScreenBrightnessController( - private val host: Activity + private val host: Activity, ) { /** * screen brightness saved by [save] @@ -68,4 +68,4 @@ class ScreenBrightnessController( attr.screenBrightness = value host.window.attributes = attr } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/SwipeZonesController.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/SwipeZonesController.kt index 93501bf0..58fddeb5 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/SwipeZonesController.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/SwipeZonesController.kt @@ -3,12 +3,12 @@ package app.revanced.integrations.youtube.swipecontrols.controller import android.app.Activity import android.util.TypedValue import android.view.ViewGroup +import app.revanced.integrations.shared.Utils import app.revanced.integrations.youtube.swipecontrols.misc.Rectangle import app.revanced.integrations.youtube.swipecontrols.misc.applyDimension -import app.revanced.integrations.shared.Utils import kotlin.math.min -/** +/** * Y- Axis: * -------- 0 * ^ @@ -36,7 +36,7 @@ import kotlin.math.min @Suppress("PrivatePropertyName") class SwipeZonesController( private val host: Activity, - private val fallbackScreenRect: () -> Rectangle + private val fallbackScreenRect: () -> Rectangle, ) { /** * 20dp, in pixels @@ -74,7 +74,7 @@ class SwipeZonesController( p.x + _20dp, p.y + _40dp, p.width - _20dp, - p.height - _20dp - _80dp + p.height - _20dp - _80dp, ) } @@ -89,7 +89,7 @@ class SwipeZonesController( eRect.right - zoneWidth, eRect.top, zoneWidth, - eRect.height + eRect.height, ) } @@ -103,7 +103,7 @@ class SwipeZonesController( effectiveSwipeRect.left, effectiveSwipeRect.top, zoneWidth, - effectiveSwipeRect.height + effectiveSwipeRect.height, ) } @@ -137,8 +137,8 @@ class SwipeZonesController( playerView.x.toInt(), playerView.y.toInt(), min(playerView.width, playerWidthWithPadding), - playerView.height + playerView.height, ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/VolumeKeysController.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/VolumeKeysController.kt index 46c82aac..000634b6 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/VolumeKeysController.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/VolumeKeysController.kt @@ -9,7 +9,7 @@ import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity * @param controller main controller instance */ class VolumeKeysController( - private val controller: SwipeControlsHostActivity + private val controller: SwipeControlsHostActivity, ) { /** * key event handler @@ -18,7 +18,7 @@ class VolumeKeysController( * @return consume the event? */ fun onKeyEvent(event: KeyEvent): Boolean { - if(!controller.config.overwriteVolumeKeyControls) { + if (!controller.config.overwriteVolumeKeyControls) { return false } diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/ClassicSwipeController.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/ClassicSwipeController.kt index e4cb9cd2..3f1c468f 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/ClassicSwipeController.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/ClassicSwipeController.kt @@ -15,7 +15,7 @@ import app.revanced.integrations.youtube.swipecontrols.misc.toPoint * @param controller reference to the main swipe controller */ class ClassicSwipeController( - private val controller: SwipeControlsHostActivity + private val controller: SwipeControlsHostActivity, ) : BaseGestureController(controller), PlayerControlsVisibilityObserver by PlayerControlsVisibilityObserverImpl(controller) { /** @@ -27,10 +27,16 @@ class ClassicSwipeController( get() = currentSwipe == SwipeDetector.SwipeDirection.VERTICAL override fun isInSwipeZone(motionEvent: MotionEvent): Boolean { - val inVolumeZone = if (controller.config.enableVolumeControls) - (motionEvent.toPoint() in controller.zones.volume) else false - val inBrightnessZone = if (controller.config.enableBrightnessControl) - (motionEvent.toPoint() in controller.zones.brightness) else false + val inVolumeZone = if (controller.config.enableVolumeControls) { + (motionEvent.toPoint() in controller.zones.volume) + } else { + false + } + val inBrightnessZone = if (controller.config.enableBrightnessControl) { + (motionEvent.toPoint() in controller.zones.brightness) + } else { + false + } return inVolumeZone || inBrightnessZone } @@ -92,7 +98,7 @@ class ClassicSwipeController( from: MotionEvent, to: MotionEvent, distanceX: Double, - distanceY: Double + distanceY: Double, ): Boolean { // cancel if not vertical if (currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/PressToSwipeController.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/PressToSwipeController.kt index bf391462..84faf385 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/PressToSwipeController.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/PressToSwipeController.kt @@ -13,7 +13,7 @@ import app.revanced.integrations.youtube.swipecontrols.misc.toPoint * @param controller reference to the main swipe controller */ class PressToSwipeController( - private val controller: SwipeControlsHostActivity + private val controller: SwipeControlsHostActivity, ) : BaseGestureController(controller) { /** * monitors if the user is currently in a swipe session. @@ -26,10 +26,16 @@ class PressToSwipeController( override fun shouldDropMotion(motionEvent: MotionEvent): Boolean = false override fun isInSwipeZone(motionEvent: MotionEvent): Boolean { - val inVolumeZone = if (controller.config.enableVolumeControls) - (motionEvent.toPoint() in controller.zones.volume) else false - val inBrightnessZone = if (controller.config.enableBrightnessControl) - (motionEvent.toPoint() in controller.zones.brightness) else false + val inVolumeZone = if (controller.config.enableVolumeControls) { + (motionEvent.toPoint() in controller.zones.volume) + } else { + false + } + val inBrightnessZone = if (controller.config.enableBrightnessControl) { + (motionEvent.toPoint() in controller.zones.brightness) + } else { + false + } return inVolumeZone || inBrightnessZone } @@ -53,7 +59,7 @@ class PressToSwipeController( from: MotionEvent, to: MotionEvent, distanceX: Double, - distanceY: Double + distanceY: Double, ): Boolean { // cancel if not in swipe session or vertical if (!isInSwipeSession || currentSwipe != SwipeDetector.SwipeDirection.VERTICAL) return false diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/BaseGestureController.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/BaseGestureController.kt index b3c54e44..fb5a1b04 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/BaseGestureController.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/BaseGestureController.kt @@ -11,11 +11,11 @@ import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity * @param controller reference to the main swipe controller */ abstract class BaseGestureController( - private val controller: SwipeControlsHostActivity + private val controller: SwipeControlsHostActivity, ) : GestureController, GestureDetector.SimpleOnGestureListener(), SwipeDetector by SwipeDetectorImpl( - controller.config.swipeMagnitudeThreshold.toDouble() + controller.config.swipeMagnitudeThreshold.toDouble(), ), VolumeAndBrightnessScroller by VolumeAndBrightnessScrollerImpl( controller, @@ -23,7 +23,7 @@ abstract class BaseGestureController( controller.screen, controller.overlay, 10, - 1 + 1, ) { /** @@ -85,7 +85,7 @@ abstract class BaseGestureController( from: MotionEvent, to: MotionEvent, distanceX: Float, - distanceY: Float + distanceY: Float, ): Boolean { // submit to swipe detector submitForSwipe(from, to, distanceX, distanceY) @@ -96,7 +96,7 @@ abstract class BaseGestureController( from, to, distanceX.toDouble(), - distanceY.toDouble() + distanceY.toDouble(), ) // if the swipe was consumed, cancel downstream events once @@ -110,7 +110,9 @@ abstract class BaseGestureController( } consumed - } else false + } else { + false + } } /** @@ -149,6 +151,6 @@ abstract class BaseGestureController( from: MotionEvent, to: MotionEvent, distanceX: Double, - distanceY: Double + distanceY: Double, ): Boolean } diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/SwipeDetector.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/SwipeDetector.kt index 078acfce..3dc5cc42 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/SwipeDetector.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/SwipeDetector.kt @@ -25,7 +25,7 @@ interface SwipeDetector { from: MotionEvent, to: MotionEvent, distanceX: Float, - distanceY: Float + distanceY: Float, ) /** @@ -50,7 +50,7 @@ interface SwipeDetector { /** * swipe along the Y- Axes */ - VERTICAL + VERTICAL, } } @@ -60,7 +60,7 @@ interface SwipeDetector { * @param swipeMagnitudeThreshold minimum magnitude before a swipe is detected as such */ class SwipeDetectorImpl( - private val swipeMagnitudeThreshold: Double + private val swipeMagnitudeThreshold: Double, ) : SwipeDetector { override var currentSwipe = SwipeDetector.SwipeDirection.NONE @@ -68,7 +68,7 @@ class SwipeDetectorImpl( from: MotionEvent, to: MotionEvent, distanceX: Float, - distanceY: Float + distanceY: Float, ) { if (currentSwipe == SwipeDetector.SwipeDirection.NONE) { // no swipe direction was detected yet, try to detect one diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/VolumeAndBrightnessScroller.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/VolumeAndBrightnessScroller.kt index 758e584a..dec14b81 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/VolumeAndBrightnessScroller.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/controller/gesture/core/VolumeAndBrightnessScroller.kt @@ -48,7 +48,7 @@ class VolumeAndBrightnessScrollerImpl( private val screenController: ScreenBrightnessController?, private val overlayController: SwipeControlsOverlay, volumeDistance: Int = 10, - brightnessDistance: Int = 1 + brightnessDistance: Int = 1, ) : VolumeAndBrightnessScroller { // region volume @@ -56,8 +56,8 @@ class VolumeAndBrightnessScrollerImpl( ScrollDistanceHelper( volumeDistance.applyDimension( context, - TypedValue.COMPLEX_UNIT_DIP - ) + TypedValue.COMPLEX_UNIT_DIP, + ), ) { _, _, direction -> volumeController?.run { volume += direction @@ -73,8 +73,8 @@ class VolumeAndBrightnessScrollerImpl( ScrollDistanceHelper( brightnessDistance.applyDimension( context, - TypedValue.COMPLEX_UNIT_DIP - ) + TypedValue.COMPLEX_UNIT_DIP, + ), ) { _, _, direction -> screenController?.run { if (screenBrightness > 0 || direction > 0) { diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/Point.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/Point.kt index eaca3270..f3d8a966 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/Point.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/Point.kt @@ -7,7 +7,7 @@ import android.view.MotionEvent */ data class Point( val x: Int, - val y: Int + val y: Int, ) /** diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/Rectangle.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/Rectangle.kt index 5a3255e7..3195251d 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/Rectangle.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/Rectangle.kt @@ -7,7 +7,7 @@ data class Rectangle( val x: Int, val y: Int, val width: Int, - val height: Int + val height: Int, ) { val left = x val right = x + width @@ -15,7 +15,6 @@ data class Rectangle( val bottom = y + height } - /** * is the point within this rectangle? */ diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/ScrollDistanceHelper.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/ScrollDistanceHelper.kt index eb7082ce..a85dbac8 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/ScrollDistanceHelper.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/ScrollDistanceHelper.kt @@ -11,7 +11,7 @@ import kotlin.math.sign */ class ScrollDistanceHelper( private val unitDistance: Int, - private val callback: (oldDistance: Double, newDistance: Double, direction: Int) -> Unit + private val callback: (oldDistance: Double, newDistance: Double, direction: Int) -> Unit, ) { /** @@ -35,7 +35,7 @@ class ScrollDistanceHelper( callback.invoke( oldDistance, scrolledDistance, - sign(scrolledDistance).toInt() + sign(scrolledDistance).toInt(), ) } } @@ -53,4 +53,4 @@ class ScrollDistanceHelper( private fun subtractUnitDistance() { scrolledDistance -= (unitDistance * sign(scrolledDistance)) } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/SwipeControlsOverlay.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/SwipeControlsOverlay.kt index b4155ccf..644cb23f 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/SwipeControlsOverlay.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/SwipeControlsOverlay.kt @@ -23,4 +23,4 @@ interface SwipeControlsOverlay { * called when a new swipe- session has started */ fun onEnterSwipeSession() -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/SwipeControlsUtils.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/SwipeControlsUtils.kt index a197c510..141dc222 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/SwipeControlsUtils.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/misc/SwipeControlsUtils.kt @@ -20,7 +20,6 @@ fun Int.applyDimension(context: Context, unit: Int): Int { return TypedValue.applyDimension( unit, this.toFloat(), - context.resources.displayMetrics + context.resources.displayMetrics, ).roundToInt() } - diff --git a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/views/SwipeControlsOverlayLayout.kt b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/views/SwipeControlsOverlayLayout.kt index d5f0c25a..dbe2434f 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/views/SwipeControlsOverlayLayout.kt +++ b/app/src/main/java/app/revanced/integrations/youtube/swipecontrols/views/SwipeControlsOverlayLayout.kt @@ -11,10 +11,10 @@ import android.view.View import android.view.ViewGroup import android.widget.RelativeLayout import android.widget.TextView +import app.revanced.integrations.shared.Utils import app.revanced.integrations.youtube.swipecontrols.SwipeControlsConfigurationProvider import app.revanced.integrations.youtube.swipecontrols.misc.SwipeControlsOverlay import app.revanced.integrations.youtube.swipecontrols.misc.applyDimension -import app.revanced.integrations.shared.Utils import kotlin.math.round /** @@ -24,7 +24,7 @@ import kotlin.math.round */ class SwipeControlsOverlayLayout( context: Context, - private val config: SwipeControlsConfigurationProvider + private val config: SwipeControlsConfigurationProvider, ) : RelativeLayout(context), SwipeControlsOverlay { /** * DO NOT use this, for tools only @@ -40,14 +40,14 @@ class SwipeControlsOverlayLayout( private fun getDrawable(name: String, width: Int, height: Int): Drawable { return resources.getDrawable( Utils.getResourceIdentifier(context, name, "drawable"), - context.theme + context.theme, ).apply { setTint(config.overlayForegroundColor) setBounds( 0, 0, width, - height + height, ) } } @@ -59,14 +59,14 @@ class SwipeControlsOverlayLayout( feedbackTextView = TextView(context).apply { layoutParams = LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ).apply { addRule(CENTER_IN_PARENT, TRUE) setPadding( feedbackTextViewPadding, feedbackTextViewPadding, feedbackTextViewPadding, - feedbackTextViewPadding + feedbackTextViewPadding, ) } background = GradientDrawable().apply { @@ -108,7 +108,7 @@ class SwipeControlsOverlayLayout( icon, null, null, - null + null, ) visibility = VISIBLE } @@ -117,7 +117,7 @@ class SwipeControlsOverlayLayout( override fun onVolumeChanged(newVolume: Int, maximumVolume: Int) { showFeedbackView( "$newVolume", - if (newVolume > 0) normalVolumeIcon else mutedVolumeIcon + if (newVolume > 0) normalVolumeIcon else mutedVolumeIcon, ) } @@ -134,8 +134,8 @@ class SwipeControlsOverlayLayout( @Suppress("DEPRECATION") performHapticFeedback( HapticFeedbackConstants.LONG_PRESS, - HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING + HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, ) } } -} \ No newline at end of file +} diff --git a/stub/build.gradle.kts b/stub/build.gradle.kts index d16c19b7..c874e310 100644 --- a/stub/build.gradle.kts +++ b/stub/build.gradle.kts @@ -16,7 +16,7 @@ android { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } From eee3f352c59141f47f6bda6c6cd350f1a16f1450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ho=C3=A0ng=20Gia=20B=E1=BA=A3o?= <70064328+YT-Advanced@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:52:18 +0700 Subject: [PATCH 05/40] fix(YouTube - Client spoof): Allow playback for links with timestamp (#582) --- .../youtube/patches/spoof/SpoofSignaturePatch.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch.java index e958f884..531d2b81 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch.java @@ -90,7 +90,7 @@ public class SpoofSignaturePatch { try { Logger.printDebug(() -> "Original protobuf parameter value: " + parameters); - if (!Settings.SPOOF_SIGNATURE.get()) { + if (parameters == null || !Settings.SPOOF_SIGNATURE.get()) { return parameters; } @@ -98,7 +98,7 @@ public class SpoofSignaturePatch { // For this reason, the player parameters of a clip are usually very long (150~300 characters). // Clips are 60 seconds or less in length, so no spoofing. //noinspection AssignmentUsedAsCondition - if (useOriginalStoryboardRenderer = parameters.length() > 150 || containsAny(parameters, CLIPS_PARAMETERS)) { + if (useOriginalStoryboardRenderer = parameters.length() > 150 || parameters.startsWith(CLIPS_PARAMETERS)) { return parameters; } From c7ae3355a15441369565c7f44c4a13ce59d97d64 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 4 Mar 2024 06:55:30 +0000 Subject: [PATCH 06/40] chore(release): 1.4.1-dev.2 [skip ci] ## [1.4.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.1...v1.4.1-dev.2) (2024-03-04) ### Bug Fixes * **YouTube - Client spoof:** Allow playback for links with timestamp ([#582](https://github.com/ReVanced/revanced-integrations/issues/582)) ([eee3f35](https://github.com/ReVanced/revanced-integrations/commit/eee3f352c59141f47f6bda6c6cd350f1a16f1450)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17799b23..7f46c9de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.4.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.1...v1.4.1-dev.2) (2024-03-04) + + +### Bug Fixes + +* **YouTube - Client spoof:** Allow playback for links with timestamp ([#582](https://github.com/ReVanced/revanced-integrations/issues/582)) ([eee3f35](https://github.com/ReVanced/revanced-integrations/commit/eee3f352c59141f47f6bda6c6cd350f1a16f1450)) + ## [1.4.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.4.0...v1.4.1-dev.1) (2024-03-02) diff --git a/gradle.properties b/gradle.properties index 248aadf6..63533bd1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.4.1-dev.1 +version = 1.4.1-dev.2 From ab07a563b9ef890dc8a673eeb4268ce1c9f15a69 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:33:09 +0400 Subject: [PATCH 07/40] fix: Revert AGP dependency update --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d2b86313..205aac25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.3.0" +agp = "8.2.2" # 8.3.0 causes java verifier error: https://github.com/ReVanced/revanced-patches/issues/2818 annotation = "1.7.1" kotlin = "1.9.22" appcompat = "1.7.0-alpha03" From 6188fa75c66174013b1e4de6faf6e65b6000f9e9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 4 Mar 2024 09:42:57 +0000 Subject: [PATCH 08/40] chore(release): 1.4.1-dev.3 [skip ci] ## [1.4.1-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.2...v1.4.1-dev.3) (2024-03-04) ### Bug Fixes * Revert AGP dependency update ([ab07a56](https://github.com/ReVanced/revanced-integrations/commit/ab07a563b9ef890dc8a673eeb4268ce1c9f15a69)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f46c9de..8b5d73ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.4.1-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.2...v1.4.1-dev.3) (2024-03-04) + + +### Bug Fixes + +* Revert AGP dependency update ([ab07a56](https://github.com/ReVanced/revanced-integrations/commit/ab07a563b9ef890dc8a673eeb4268ce1c9f15a69)) + ## [1.4.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.1...v1.4.1-dev.2) (2024-03-04) diff --git a/gradle.properties b/gradle.properties index 63533bd1..7159b6fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.4.1-dev.2 +version = 1.4.1-dev.3 From 8d48a90a8b8bc7ce9e22580b7a66c4c12fd6d54f Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:44:56 +0400 Subject: [PATCH 09/40] fix(YouTube - Hide seekbar): Use original seekbar color if Theme patch is not included (#580) --- .../app/revanced/integrations/youtube/settings/Settings.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index 7967f6a7..5467e366 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -147,9 +147,9 @@ public class Settings extends BaseSettings { // Seekbar public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE); - public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE); + public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true); public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE); - public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", TRUE, true); + public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", FALSE, true); public static final StringSetting SEEKBAR_CUSTOM_COLOR_VALUE = new StringSetting("revanced_seekbar_custom_color_value", "#FF0000", true, parent(SEEKBAR_CUSTOM_COLOR)); // Action buttons From 6aacd1a2252ad2aec032a693ab150c8439790baf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 4 Mar 2024 11:47:49 +0000 Subject: [PATCH 10/40] chore(release): 1.4.1-dev.4 [skip ci] ## [1.4.1-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.3...v1.4.1-dev.4) (2024-03-04) ### Bug Fixes * **YouTube - Hide seekbar:** Use original seekbar color if Theme patch is not included ([#580](https://github.com/ReVanced/revanced-integrations/issues/580)) ([8d48a90](https://github.com/ReVanced/revanced-integrations/commit/8d48a90a8b8bc7ce9e22580b7a66c4c12fd6d54f)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b5d73ef..4e867f47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.4.1-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.3...v1.4.1-dev.4) (2024-03-04) + + +### Bug Fixes + +* **YouTube - Hide seekbar:** Use original seekbar color if Theme patch is not included ([#580](https://github.com/ReVanced/revanced-integrations/issues/580)) ([8d48a90](https://github.com/ReVanced/revanced-integrations/commit/8d48a90a8b8bc7ce9e22580b7a66c4c12fd6d54f)) + ## [1.4.1-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.2...v1.4.1-dev.3) (2024-03-04) diff --git a/gradle.properties b/gradle.properties index 7159b6fa..5774c3b0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.4.1-dev.3 +version = 1.4.1-dev.4 From 771a0de3bc9bec3ec5a8e4f8b02edfa9df7b1997 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 4 Mar 2024 20:36:24 +0100 Subject: [PATCH 11/40] feat(YouTube - External downloader): Add ability to use in-app download button --- .../youtube/patches/DownloadsPatch.java | 57 +++++++++++++++++++ .../youtube/settings/Settings.java | 1 + .../videoplayer/ExternalDownloadButton.java | 39 +------------ 3 files changed, 60 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java new file mode 100644 index 00000000..7147284d --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java @@ -0,0 +1,57 @@ +package app.revanced.integrations.youtube.patches; + +import android.content.Intent; +import android.content.pm.PackageManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import app.revanced.integrations.shared.Logger; +import app.revanced.integrations.shared.StringRef; +import app.revanced.integrations.shared.Utils; +import app.revanced.integrations.youtube.settings.Settings; + +public final class DownloadsPatch { + public static boolean inAppDownloadButtonOnClick() { + if (!Settings.USE_IN_APP_DOWNLOAD_BUTTON.get()) + return false; + + launchExternalDownloader(); + return true; + } + + public static void launchExternalDownloader() { + Logger.printDebug(() -> "Launching external downloader"); + + final var context = Utils.getContext(); + + // Trim string to avoid any accidental whitespace. + var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim(); + + boolean packageEnabled = false; + try { + packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled; + } catch (PackageManager.NameNotFoundException error) { + Logger.printDebug(() -> "External downloader could not be found: " + error); + } + + // If the package is not installed, show the toast + if (!packageEnabled) { + Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName)); + return; + } + + // Launch intent + try { + String content = String.format("https://youtu.be/%s", VideoInformation.getVideoId()); + + Intent intent = new Intent("android.intent.action.SEND"); + intent.setType("text/plain"); + intent.setPackage(downloaderPackageName); + intent.putExtra("android.intent.extra.TEXT", content); + context.startActivity(intent); + + Logger.printDebug(() -> "Launched the intent with the content: " + content); + } catch (Exception error) { + Logger.printException(() -> "Failed to launch the intent: " + error, error); + } + } +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index 5467e366..2ed879db 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -18,6 +18,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE); public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name", "org.schabi.newpipe" /* NewPipe */, parent(EXTERNAL_DOWNLOADER)); + public static final BooleanSetting USE_IN_APP_DOWNLOAD_BUTTON = new BooleanSetting("revanced_use_in_app_download_button", TRUE); // Copy video URL public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE); diff --git a/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java b/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java index 5d1498fc..a02c2023 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java +++ b/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java @@ -1,17 +1,14 @@ package app.revanced.integrations.youtube.videoplayer; -import android.content.Intent; -import android.content.pm.PackageManager; import android.view.View; import android.view.ViewGroup; import androidx.annotation.Nullable; +import app.revanced.integrations.youtube.patches.DownloadsPatch; import app.revanced.integrations.youtube.patches.VideoInformation; import app.revanced.integrations.youtube.settings.Settings; import app.revanced.integrations.shared.Logger; -import app.revanced.integrations.shared.Utils; -import app.revanced.integrations.shared.StringRef; @SuppressWarnings("unused") public class ExternalDownloadButton extends BottomControlButton { @@ -47,39 +44,7 @@ public class ExternalDownloadButton extends BottomControlButton { } private static void onDownloadClick(View view) { - Logger.printDebug(() -> "External download button clicked"); - - final var context = view.getContext(); - // Trim string to avoid any accidental whitespace. - var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim(); - - boolean packageEnabled = false; - try { - packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled; - } catch (PackageManager.NameNotFoundException error) { - Logger.printDebug(() -> "External downloader could not be found: " + error); - } - - // If the package is not installed, show the toast - if (!packageEnabled) { - Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName)); - return; - } - - // Launch PowerTube intent - try { - String content = String.format("https://youtu.be/%s", VideoInformation.getVideoId()); - - Intent intent = new Intent("android.intent.action.SEND"); - intent.setType("text/plain"); - intent.setPackage(downloaderPackageName); - intent.putExtra("android.intent.extra.TEXT", content); - context.startActivity(intent); - - Logger.printDebug(() -> "Launched the intent with the content: " + content); - } catch (Exception error) { - Logger.printException(() -> "Failed to launch the intent: " + error, error); - } + DownloadsPatch.launchExternalDownloader(); } } From 75a494e09ba6fcdf3dfea5e51131cbcce57142ed Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 4 Mar 2024 19:40:00 +0000 Subject: [PATCH 12/40] chore(release): 1.5.0-dev.1 [skip ci] # [1.5.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.4...v1.5.0-dev.1) (2024-03-04) ### Features * **YouTube - External downloader:** Add ability to use in-app download button ([771a0de](https://github.com/ReVanced/revanced-integrations/commit/771a0de3bc9bec3ec5a8e4f8b02edfa9df7b1997)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e867f47..6fc7b8ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.5.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.4...v1.5.0-dev.1) (2024-03-04) + + +### Features + +* **YouTube - External downloader:** Add ability to use in-app download button ([771a0de](https://github.com/ReVanced/revanced-integrations/commit/771a0de3bc9bec3ec5a8e4f8b02edfa9df7b1997)) + ## [1.4.1-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.3...v1.4.1-dev.4) (2024-03-04) diff --git a/gradle.properties b/gradle.properties index 5774c3b0..88cafa8c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.4.1-dev.4 +version = 1.5.0-dev.1 From 468dfac0544e282658675a8be65b4e43aa351068 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 8 Mar 2024 09:10:06 +0400 Subject: [PATCH 13/40] fix(YouTube - Downloads): Use new task context (#583) Co-authored-by: oSumAtrIX --- .../revanced/integrations/shared/Utils.java | 7 +- .../youtube/patches/DownloadsPatch.java | 81 ++++++++++++++++--- .../patches/HideBreakingNewsPatch.java | 2 +- .../patches/PlayerOverlaysHookPatch.java | 16 +--- .../patches/ReturnYouTubeDislikePatch.java | 2 +- .../patches/spoof/SpoofAppVersionPatch.java | 22 +---- .../ReturnYouTubeDislike.java | 2 +- .../youtube/settings/Settings.java | 14 +++- .../ReVancedPreferenceFragment.java | 21 ++++- .../videoplayer/ExternalDownloadButton.java | 7 +- 10 files changed, 117 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/shared/Utils.java b/app/src/main/java/app/revanced/integrations/shared/Utils.java index c2aa2f6b..f92c9cfd 100644 --- a/app/src/main/java/app/revanced/integrations/shared/Utils.java +++ b/app/src/main/java/app/revanced/integrations/shared/Utils.java @@ -40,7 +40,7 @@ import kotlin.text.Regex; public class Utils { @SuppressLint("StaticFieldLeak") - public static Context context; + private static Context context; private static String versionName; @@ -233,6 +233,11 @@ public class Utils { return context; } + public static void setContext(Context appContext) { + context = appContext; + Logger.printDebug(() -> "Set context: " + appContext); // Cannot log before context is set. + } + public static void setClipboard(@NonNull String text) { android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text); diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java index 7147284d..9904ff75 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java @@ -1,27 +1,84 @@ package app.revanced.integrations.youtube.patches; +import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + +import java.lang.ref.WeakReference; + import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.StringRef; import app.revanced.integrations.shared.Utils; +import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch; import app.revanced.integrations.youtube.settings.Settings; +@SuppressWarnings("unused") public final class DownloadsPatch { - public static boolean inAppDownloadButtonOnClick() { - if (!Settings.USE_IN_APP_DOWNLOAD_BUTTON.get()) - return false; - launchExternalDownloader(); - return true; + private static WeakReference activityRef = new WeakReference<>(null); + + /** + * Injection point. + */ + public static void activityCreated(Activity mainActivity) { + activityRef = new WeakReference<>(mainActivity); } - public static void launchExternalDownloader() { - Logger.printDebug(() -> "Launching external downloader"); + /** + * Injection point. + * + * Call if download playlist is pressed, or if download button is used + * for old spoofed version (both playlists and the player action button). + * + * Downloading playlists is not supported yet, + * as the hooked code does not easily expose the playlist id. + */ + public static boolean inAppDownloadPlaylistLegacyOnClick(@Nullable String videoId) { + if (videoId == null || videoId.isEmpty()) { + // videoId is null or empty if download playlist is pressed. + Logger.printDebug(() -> "Ignoring playlist download button press"); + return false; + } + return inAppDownloadButtonOnClick(); + } - final var context = Utils.getContext(); + /** + * Injection point. + */ + public static boolean inAppDownloadButtonOnClick() { + try { + if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) { + return false; + } + + // If possible, use the main activity as the context. + // Otherwise fall back on using the application context. + Context context = activityRef.get(); + boolean isActivityContext = true; + if (context == null) { + // Utils context is the application context, and not an activity context. + context = Utils.getContext(); + isActivityContext = false; + } + + launchExternalDownloader(context, isActivityContext); + return true; + } catch (Exception ex) { + Logger.printException(() -> "inAppDownloadButtonOnClick failure", ex); + } + return false; + } + + /** + * @param isActivityContext If the context parameter is for an Activity. If this is false, then + * the downloader is opened as a new task (which forces YT to minimize). + */ + public static void launchExternalDownloader(@NonNull Context context, boolean isActivityContext) { + Logger.printDebug(() -> "Launching external downloader with context: " + context); // Trim string to avoid any accidental whitespace. var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim(); @@ -47,11 +104,13 @@ public final class DownloadsPatch { intent.setType("text/plain"); intent.setPackage(downloaderPackageName); intent.putExtra("android.intent.extra.TEXT", content); + if (!isActivityContext) { + Logger.printDebug(() -> "Using new task intent"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } context.startActivity(intent); - - Logger.printDebug(() -> "Launched the intent with the content: " + content); } catch (Exception error) { - Logger.printException(() -> "Failed to launch the intent: " + error, error); + Logger.printException(() -> "Failed to launch intent: " + error, error); } } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/HideBreakingNewsPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/HideBreakingNewsPatch.java index fe9e57c0..bd6d89fa 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/HideBreakingNewsPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/HideBreakingNewsPatch.java @@ -16,7 +16,7 @@ public class HideBreakingNewsPatch { * Breaking news does not appear to be present in these older versions anyways. */ private static final boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory = - SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("17.31.00"); + SpoofAppVersionPatch.isSpoofingToLessThan("18.01.00"); /** * Injection point. diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/PlayerOverlaysHookPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/PlayerOverlaysHookPatch.java index 7070d8a5..32732cdc 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/PlayerOverlaysHookPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/PlayerOverlaysHookPatch.java @@ -6,24 +6,12 @@ import androidx.annotation.Nullable; import app.revanced.integrations.youtube.shared.PlayerOverlays; -/** - * Hook receiver class for 'player-overlays-hook' patch - * - * @usedBy app.revanced.patches.youtube.misc.playeroverlay.patch.PlayerOverlaysHookPatch - * @smali Lapp/revanced/integrations/patches/PlayerOverlaysHookPatch; - */ @SuppressWarnings("unused") public class PlayerOverlaysHookPatch { /** * Injection point. - * - * @param thisRef reference to the view - * @smali YouTubePlayerOverlaysLayout_onFinishInflateHook(Ljava / lang / Object ;)V */ - public static void YouTubePlayerOverlaysLayout_onFinishInflateHook(@Nullable Object thisRef) { - if (thisRef == null) return; - if (thisRef instanceof ViewGroup) { - PlayerOverlays.attach((ViewGroup) thisRef); - } + public static void playerOverlayInflated(ViewGroup group) { + PlayerOverlays.attach(group); } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java index 0cb687f6..4938fb37 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java @@ -46,7 +46,7 @@ import static app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTu public class ReturnYouTubeDislikePatch { public static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER = - SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.33.40"); + SpoofAppVersionPatch.isSpoofingToLessThan("18.34.00"); /** * RYD data for the current video on screen. diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofAppVersionPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofAppVersionPatch.java index 93219797..3f9f850b 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofAppVersionPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofAppVersionPatch.java @@ -1,26 +1,12 @@ package app.revanced.integrations.youtube.patches.spoof; -import app.revanced.integrations.shared.Logger; import app.revanced.integrations.youtube.settings.Settings; @SuppressWarnings("unused") public class SpoofAppVersionPatch { - private static final boolean SPOOF_APP_VERSION_ENABLED; - private static final String SPOOF_APP_VERSION_TARGET; - - static { - // TODO: remove this migration code - // Spoof targets below 17.33 that no longer reliably work. - if (Settings.SPOOF_APP_VERSION_TARGET.get().compareTo("17.33.01") < 0) { - Logger.printInfo(() -> "Resetting spoof app version target"); - Settings.SPOOF_APP_VERSION_TARGET.resetToDefault(); - } - // End migration - - SPOOF_APP_VERSION_ENABLED = Settings.SPOOF_APP_VERSION.get(); - SPOOF_APP_VERSION_TARGET = Settings.SPOOF_APP_VERSION_TARGET.get(); - } + private static final boolean SPOOF_APP_VERSION_ENABLED = Settings.SPOOF_APP_VERSION.get(); + private static final String SPOOF_APP_VERSION_TARGET = Settings.SPOOF_APP_VERSION_TARGET.get(); /** * Injection point @@ -30,8 +16,8 @@ public class SpoofAppVersionPatch { return version; } - public static boolean isSpoofingToEqualOrLessThan(String version) { - return SPOOF_APP_VERSION_ENABLED && SPOOF_APP_VERSION_TARGET.compareTo(version) <= 0; + public static boolean isSpoofingToLessThan(String version) { + return SPOOF_APP_VERSION_ENABLED && SPOOF_APP_VERSION_TARGET.compareTo(version) < 0; } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/ReturnYouTubeDislike.java b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/ReturnYouTubeDislike.java index 7e7fe68a..bfff1b15 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/ReturnYouTubeDislike.java @@ -91,7 +91,7 @@ public class ReturnYouTubeDislike { private static final char MIDDLE_SEPARATOR_CHARACTER = 'â—Ž'; // 'bullseye' private static final boolean IS_SPOOFING_TO_OLD_SEPARATOR_COLOR - = SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.09.39"); + = SpoofAppVersionPatch.isSpoofingToLessThan("18.10.00"); /** * Cached lookup of all video ids. diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index 2ed879db..6122e8af 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -1,7 +1,9 @@ package app.revanced.integrations.youtube.settings; +import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.settings.*; import app.revanced.integrations.shared.settings.preference.SharedPrefCategory; +import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch; import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings; import java.util.Arrays; @@ -16,9 +18,9 @@ import static java.lang.Boolean.TRUE; public class Settings extends BaseSettings { // External downloader public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE); + public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE); public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name", - "org.schabi.newpipe" /* NewPipe */, parent(EXTERNAL_DOWNLOADER)); - public static final BooleanSetting USE_IN_APP_DOWNLOAD_BUTTON = new BooleanSetting("revanced_use_in_app_download_button", TRUE); + "org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON)); // Copy video URL public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE); @@ -335,6 +337,14 @@ public class Settings extends BaseSettings { // and more time should be given for users who rarely upgrade. migrateOldSettingToNew(DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID); + + // Old spoof versions that no longer work reliably. + if (SpoofAppVersionPatch.isSpoofingToLessThan("17.33.00")) { + Logger.printInfo(() -> "Resetting spoof app version target"); + Settings.SPOOF_APP_VERSION_TARGET.resetToDefault(); + } + + // endregion } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java index 5542d61a..1f3bd6c0 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -1,8 +1,15 @@ package app.revanced.integrations.youtube.settings.preference; +import android.os.Build; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceGroup; + +import androidx.annotation.RequiresApi; + +import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.settings.preference.AbstractPreferenceFragment; +import app.revanced.integrations.youtube.patches.DownloadsPatch; import app.revanced.integrations.youtube.patches.playback.speed.CustomPlaybackSpeedPatch; import app.revanced.integrations.youtube.settings.Settings; @@ -12,14 +19,20 @@ import app.revanced.integrations.youtube.settings.Settings; * @noinspection deprecation */ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment { + + @RequiresApi(api = Build.VERSION_CODES.O) @Override protected void initialize() { super.initialize(); - // If the preference was included, then initialize it based on the available playback speed - Preference defaultSpeedPreference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key); - if (defaultSpeedPreference instanceof ListPreference) { - CustomPlaybackSpeedPatch.initializeListPreference((ListPreference) defaultSpeedPreference); + try { + // If the preference was included, then initialize it based on the available playback speed. + Preference defaultSpeedPreference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key); + if (defaultSpeedPreference instanceof ListPreference) { + CustomPlaybackSpeedPatch.initializeListPreference((ListPreference) defaultSpeedPreference); + } + } catch (Exception ex) { + Logger.printException(() -> "initialize failure", ex); } } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java b/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java index a02c2023..378cde3e 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java +++ b/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java @@ -5,10 +5,9 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; -import app.revanced.integrations.youtube.patches.DownloadsPatch; -import app.revanced.integrations.youtube.patches.VideoInformation; -import app.revanced.integrations.youtube.settings.Settings; import app.revanced.integrations.shared.Logger; +import app.revanced.integrations.youtube.patches.DownloadsPatch; +import app.revanced.integrations.youtube.settings.Settings; @SuppressWarnings("unused") public class ExternalDownloadButton extends BottomControlButton { @@ -44,7 +43,7 @@ public class ExternalDownloadButton extends BottomControlButton { } private static void onDownloadClick(View view) { - DownloadsPatch.launchExternalDownloader(); + DownloadsPatch.launchExternalDownloader(view.getContext(), true); } } From d6032216e4ef5a45bc41525091ca1a00a019f4ae Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 8 Mar 2024 05:13:03 +0000 Subject: [PATCH 14/40] chore(release): 1.5.0-dev.2 [skip ci] # [1.5.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2024-03-08) ### Bug Fixes * **YouTube - Downloads:** Use new task context ([#583](https://github.com/ReVanced/revanced-integrations/issues/583)) ([468dfac](https://github.com/ReVanced/revanced-integrations/commit/468dfac0544e282658675a8be65b4e43aa351068)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fc7b8ab..eabd6ba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.5.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2024-03-08) + + +### Bug Fixes + +* **YouTube - Downloads:** Use new task context ([#583](https://github.com/ReVanced/revanced-integrations/issues/583)) ([468dfac](https://github.com/ReVanced/revanced-integrations/commit/468dfac0544e282658675a8be65b4e43aa351068)) + # [1.5.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.4.1-dev.4...v1.5.0-dev.1) (2024-03-04) diff --git a/gradle.properties b/gradle.properties index 88cafa8c..d4c0bb7b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.5.0-dev.1 +version = 1.5.0-dev.2 From b9c1eec69fab64f213dd77d1f932e3244d81ab2d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 9 Mar 2024 05:17:27 +0100 Subject: [PATCH 15/40] fix(YouTube - Disable suggested video end screen): Disable by default to fix autoplay issues (#578) --- .../app/revanced/integrations/youtube/settings/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index 6122e8af..cab2c785 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -63,7 +63,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true); public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE); public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE); - public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", TRUE); + public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE); public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE); public static final BooleanSetting HIDE_ALBUM_CARDS = new BooleanSetting("revanced_hide_album_cards", FALSE, true); public static final BooleanSetting HIDE_ARTIST_CARDS = new BooleanSetting("revanced_hide_artist_cards", FALSE); From da8ec49589c38627f0d5346bb123ffabb909a246 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 9 Mar 2024 04:20:20 +0000 Subject: [PATCH 16/40] chore(release): 1.5.0-dev.3 [skip ci] # [1.5.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.2...v1.5.0-dev.3) (2024-03-09) ### Bug Fixes * **YouTube - Disable suggested video end screen:** Disable by default to fix autoplay issues ([#578](https://github.com/ReVanced/revanced-integrations/issues/578)) ([b9c1eec](https://github.com/ReVanced/revanced-integrations/commit/b9c1eec69fab64f213dd77d1f932e3244d81ab2d)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eabd6ba2..73c4a93b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.5.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.2...v1.5.0-dev.3) (2024-03-09) + + +### Bug Fixes + +* **YouTube - Disable suggested video end screen:** Disable by default to fix autoplay issues ([#578](https://github.com/ReVanced/revanced-integrations/issues/578)) ([b9c1eec](https://github.com/ReVanced/revanced-integrations/commit/b9c1eec69fab64f213dd77d1f932e3244d81ab2d)) + # [1.5.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2024-03-08) diff --git a/gradle.properties b/gradle.properties index d4c0bb7b..814e4250 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.5.0-dev.2 +version = 1.5.0-dev.3 From b060732e861b011cac8737ed597385a3315f6057 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 11 Mar 2024 13:33:08 +0400 Subject: [PATCH 17/40] fix(YouTube - HDR auto brightness): Remove non functional and obsolete patch (#585) --- .../youtube/patches/HDRAutoBrightnessPatch.java | 6 +++++- .../revanced/integrations/youtube/settings/Settings.java | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/HDRAutoBrightnessPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/HDRAutoBrightnessPatch.java index 9ed0591c..3c644308 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/HDRAutoBrightnessPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/HDRAutoBrightnessPatch.java @@ -6,8 +6,12 @@ import app.revanced.integrations.youtube.settings.Settings; import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity; /** - * Patch class for 'hdr-auto-brightness' patch + * Patch class for 'hdr-auto-brightness' patch. + * + * Edit: This patch no longer does anything, as YT already uses BRIGHTNESS_OVERRIDE_NONE + * as the default brightness level. The hooked code was also removed from YT 19.09+ as well. */ +@Deprecated @SuppressWarnings("unused") public class HDRAutoBrightnessPatch { /** diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index cab2c785..928af8c2 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -27,7 +27,6 @@ public class Settings extends BaseSettings { public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE); // Video - public static final BooleanSetting HDR_AUTO_BRIGHTNESS = new BooleanSetting("revanced_hdr_auto_brightness", TRUE); public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE); public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", TRUE); public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2); @@ -36,6 +35,8 @@ public class Settings extends BaseSettings { public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", 1.0f); public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds", "0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true); + @Deprecated // Patch is obsolete and no longer works with 19.09+ + public static final BooleanSetting HDR_AUTO_BRIGHTNESS = new BooleanSetting("revanced_hdr_auto_brightness", TRUE); // Ads public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE); From 4be65be4145d9171e1119ecef84b2f4463f9af62 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 11 Mar 2024 09:36:16 +0000 Subject: [PATCH 18/40] chore(release): 1.5.0-dev.4 [skip ci] # [1.5.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.3...v1.5.0-dev.4) (2024-03-11) ### Bug Fixes * **YouTube - HDR auto brightness:** Remove non functional and obsolete patch ([#585](https://github.com/ReVanced/revanced-integrations/issues/585)) ([b060732](https://github.com/ReVanced/revanced-integrations/commit/b060732e861b011cac8737ed597385a3315f6057)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73c4a93b..ed02ddf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.5.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.3...v1.5.0-dev.4) (2024-03-11) + + +### Bug Fixes + +* **YouTube - HDR auto brightness:** Remove non functional and obsolete patch ([#585](https://github.com/ReVanced/revanced-integrations/issues/585)) ([b060732](https://github.com/ReVanced/revanced-integrations/commit/b060732e861b011cac8737ed597385a3315f6057)) + # [1.5.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.2...v1.5.0-dev.3) (2024-03-09) diff --git a/gradle.properties b/gradle.properties index 814e4250..346b4a17 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.5.0-dev.3 +version = 1.5.0-dev.4 From 7c40c947efc8ee0009dae2eb800dd1ddfbde46fd Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 12 Mar 2024 13:55:35 +0100 Subject: [PATCH 19/40] chore: Log intent to be opened by chooser --- .../twitter/patches/links/OpenLinksWithAppChooserPatch.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/app/revanced/integrations/twitter/patches/links/OpenLinksWithAppChooserPatch.java b/app/src/main/java/app/revanced/integrations/twitter/patches/links/OpenLinksWithAppChooserPatch.java index e781f19a..18ccaa78 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/patches/links/OpenLinksWithAppChooserPatch.java +++ b/app/src/main/java/app/revanced/integrations/twitter/patches/links/OpenLinksWithAppChooserPatch.java @@ -2,9 +2,12 @@ package app.revanced.integrations.twitter.patches.links; import android.content.Context; import android.content.Intent; +import android.util.Log; public final class OpenLinksWithAppChooserPatch { public static void openWithChooser(final Context context, final Intent intent) { + Log.d("ReVanced", "Opening intent with chooser: " + intent); + intent.setAction("android.intent.action.VIEW"); context.startActivity(Intent.createChooser(intent, null)); From 254da31d16c39781f46e1cdea1e9ba22e2fce6d1 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:32:48 +0400 Subject: [PATCH 20/40] feat(YouTube - Downloads): Use external downloader when selecting 'Download' in home feed flyout menu (#587) fix(YouTube - Downloads): Use external downloader when selecting 'Download' from home feed flyout menu --- .../youtube/patches/DownloadsPatch.java | 69 ++++++++----------- .../videoplayer/ExternalDownloadButton.java | 6 +- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java index 9904ff75..a9c37d00 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/DownloadsPatch.java @@ -6,14 +6,13 @@ import android.content.Intent; import android.content.pm.PackageManager; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import java.lang.ref.WeakReference; +import java.util.Objects; import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.StringRef; import app.revanced.integrations.shared.Utils; -import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch; import app.revanced.integrations.youtube.settings.Settings; @SuppressWarnings("unused") @@ -31,25 +30,13 @@ public final class DownloadsPatch { /** * Injection point. * - * Call if download playlist is pressed, or if download button is used - * for old spoofed version (both playlists and the player action button). + * Called from the in app download hook, + * for both the player action button (below the video) + * and the 'Download video' flyout option for feed videos. * - * Downloading playlists is not supported yet, - * as the hooked code does not easily expose the playlist id. + * Appears to always be called from the main thread. */ - public static boolean inAppDownloadPlaylistLegacyOnClick(@Nullable String videoId) { - if (videoId == null || videoId.isEmpty()) { - // videoId is null or empty if download playlist is pressed. - Logger.printDebug(() -> "Ignoring playlist download button press"); - return false; - } - return inAppDownloadButtonOnClick(); - } - - /** - * Injection point. - */ - public static boolean inAppDownloadButtonOnClick() { + public static boolean inAppDownloadButtonOnClick(@NonNull String videoId) { try { if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) { return false; @@ -65,7 +52,7 @@ public final class DownloadsPatch { isActivityContext = false; } - launchExternalDownloader(context, isActivityContext); + launchExternalDownloader(videoId, context, isActivityContext); return true; } catch (Exception ex) { Logger.printException(() -> "inAppDownloadButtonOnClick failure", ex); @@ -77,29 +64,29 @@ public final class DownloadsPatch { * @param isActivityContext If the context parameter is for an Activity. If this is false, then * the downloader is opened as a new task (which forces YT to minimize). */ - public static void launchExternalDownloader(@NonNull Context context, boolean isActivityContext) { - Logger.printDebug(() -> "Launching external downloader with context: " + context); - - // Trim string to avoid any accidental whitespace. - var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim(); - - boolean packageEnabled = false; + public static void launchExternalDownloader(@NonNull String videoId, + @NonNull Context context, boolean isActivityContext) { try { - packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled; - } catch (PackageManager.NameNotFoundException error) { - Logger.printDebug(() -> "External downloader could not be found: " + error); - } + Objects.requireNonNull(videoId); + Logger.printDebug(() -> "Launching external downloader with context: " + context); - // If the package is not installed, show the toast - if (!packageEnabled) { - Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName)); - return; - } + // Trim string to avoid any accidental whitespace. + var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim(); - // Launch intent - try { - String content = String.format("https://youtu.be/%s", VideoInformation.getVideoId()); + boolean packageEnabled = false; + try { + packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled; + } catch (PackageManager.NameNotFoundException error) { + Logger.printDebug(() -> "External downloader could not be found: " + error); + } + // If the package is not installed, show the toast + if (!packageEnabled) { + Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName)); + return; + } + + String content = "https://youtu.be/" + videoId; Intent intent = new Intent("android.intent.action.SEND"); intent.setType("text/plain"); intent.setPackage(downloaderPackageName); @@ -109,8 +96,8 @@ public final class DownloadsPatch { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } context.startActivity(intent); - } catch (Exception error) { - Logger.printException(() -> "Failed to launch intent: " + error, error); + } catch (Exception ex) { + Logger.printException(() -> "launchExternalDownloader failure", ex); } } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java b/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java index 378cde3e..2a902b02 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java +++ b/app/src/main/java/app/revanced/integrations/youtube/videoplayer/ExternalDownloadButton.java @@ -7,6 +7,7 @@ import androidx.annotation.Nullable; import app.revanced.integrations.shared.Logger; import app.revanced.integrations.youtube.patches.DownloadsPatch; +import app.revanced.integrations.youtube.patches.VideoInformation; import app.revanced.integrations.youtube.settings.Settings; @SuppressWarnings("unused") @@ -43,7 +44,10 @@ public class ExternalDownloadButton extends BottomControlButton { } private static void onDownloadClick(View view) { - DownloadsPatch.launchExternalDownloader(view.getContext(), true); + DownloadsPatch.launchExternalDownloader( + VideoInformation.getVideoId(), + view.getContext(), + true); } } From 6f1ac5d0733563d3c9007c029b870bc182132560 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 15 Mar 2024 12:35:56 +0000 Subject: [PATCH 21/40] chore(release): 1.5.0-dev.5 [skip ci] # [1.5.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.4...v1.5.0-dev.5) (2024-03-15) ### Features * **YouTube - Downloads:** Use external downloader when selecting 'Download' in home feed flyout menu ([#587](https://github.com/ReVanced/revanced-integrations/issues/587)) ([254da31](https://github.com/ReVanced/revanced-integrations/commit/254da31d16c39781f46e1cdea1e9ba22e2fce6d1)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed02ddf8..8a726beb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.5.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.4...v1.5.0-dev.5) (2024-03-15) + + +### Features + +* **YouTube - Downloads:** Use external downloader when selecting 'Download' in home feed flyout menu ([#587](https://github.com/ReVanced/revanced-integrations/issues/587)) ([254da31](https://github.com/ReVanced/revanced-integrations/commit/254da31d16c39781f46e1cdea1e9ba22e2fce6d1)) + # [1.5.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.3...v1.5.0-dev.4) (2024-03-11) diff --git a/gradle.properties b/gradle.properties index 346b4a17..aae07e14 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.5.0-dev.4 +version = 1.5.0-dev.5 From ad477e4859ef69beda297f7a2a6c29a918077628 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 16 Mar 2024 16:53:53 +0100 Subject: [PATCH 22/40] fix: Handle custom preferences (#586) * fix: Handle custom preferences --- .../AbstractPreferenceFragment.java | 73 +++++++++++-------- .../ReVancedPreferenceFragment.java | 19 +++++ 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java b/app/src/main/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java index dc1fcbd9..34a348a3 100644 --- a/app/src/main/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java +++ b/app/src/main/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java @@ -152,47 +152,60 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { } /** - * Updates a UI Preference with the {@link Setting} that backs it. + * Handles syncing a UI Preference with the {@link Setting} that backs it. * If needed, subclasses can override this to handle additional UI Preference types. * + * @param applySettingToPreference If true, then apply {@link Setting} -> Preference. + * If false, then apply {@link Setting} <- Preference. + */ + protected void syncSettingWithPreference(@NonNull Preference pref, + @NonNull Setting setting, + boolean applySettingToPreference) { + if (pref instanceof SwitchPreference) { + SwitchPreference switchPref = (SwitchPreference) pref; + BooleanSetting boolSetting = (BooleanSetting) setting; + if (applySettingToPreference) { + switchPref.setChecked(boolSetting.get()); + } else { + BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked()); + } + } else if (pref instanceof EditTextPreference) { + EditTextPreference editPreference = (EditTextPreference) pref; + if (applySettingToPreference) { + editPreference.setText(setting.get().toString()); + } else { + Setting.privateSetValueFromString(setting, editPreference.getText()); + } + } else if (pref instanceof ListPreference) { + ListPreference listPref = (ListPreference) pref; + if (applySettingToPreference) { + listPref.setValue(setting.get().toString()); + } else { + Setting.privateSetValueFromString(setting, listPref.getValue()); + } + updateListPreferenceSummary(listPref, setting); + } else { + Logger.printException(() -> "Setting cannot be handled: " + pref.getClass() + ": " + pref); + } + } + + /** + * Updates a UI Preference with the {@link Setting} that backs it. + * * @param syncSetting If the UI should be synced {@link Setting} <-> Preference * @param applySettingToPreference If true, then apply {@link Setting} -> Preference. * If false, then apply {@link Setting} <- Preference. */ - protected void updatePreference(@NonNull Preference pref, @NonNull Setting setting, - boolean syncSetting, boolean applySettingToPreference) { + private void updatePreference(@NonNull Preference pref, @NonNull Setting setting, + boolean syncSetting, boolean applySettingToPreference) { if (!syncSetting && applySettingToPreference) { throw new IllegalArgumentException(); } + if (syncSetting) { - if (pref instanceof SwitchPreference) { - SwitchPreference switchPref = (SwitchPreference) pref; - BooleanSetting boolSetting = (BooleanSetting) setting; - if (applySettingToPreference) { - switchPref.setChecked(boolSetting.get()); - } else { - BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked()); - } - } else if (pref instanceof EditTextPreference) { - EditTextPreference editPreference = (EditTextPreference) pref; - if (applySettingToPreference) { - editPreference.setText(setting.get().toString()); - } else { - Setting.privateSetValueFromString(setting, editPreference.getText()); - } - } else if (pref instanceof ListPreference) { - ListPreference listPref = (ListPreference) pref; - if (applySettingToPreference) { - listPref.setValue(setting.get().toString()); - } else { - Setting.privateSetValueFromString(setting, listPref.getValue()); - } - updateListPreferenceSummary(listPref, setting); - } else { - Logger.printException(() -> "Setting cannot be handled: " + pref.getClass() + ": " + pref); - return; - } + syncSettingWithPreference(pref, setting, applySettingToPreference); } + updatePreferenceAvailability(pref, setting); } diff --git a/app/src/main/java/app/revanced/integrations/tiktok/settings/preference/ReVancedPreferenceFragment.java b/app/src/main/java/app/revanced/integrations/tiktok/settings/preference/ReVancedPreferenceFragment.java index c90fb93b..66359350 100644 --- a/app/src/main/java/app/revanced/integrations/tiktok/settings/preference/ReVancedPreferenceFragment.java +++ b/app/src/main/java/app/revanced/integrations/tiktok/settings/preference/ReVancedPreferenceFragment.java @@ -1,11 +1,15 @@ package app.revanced.integrations.tiktok.settings.preference; +import android.preference.Preference; import android.preference.PreferenceScreen; +import androidx.annotation.NonNull; +import app.revanced.integrations.shared.settings.Setting; import app.revanced.integrations.shared.settings.preference.AbstractPreferenceFragment; import app.revanced.integrations.tiktok.settings.preference.categories.DownloadsPreferenceCategory; import app.revanced.integrations.tiktok.settings.preference.categories.FeedFilterPreferenceCategory; import app.revanced.integrations.tiktok.settings.preference.categories.IntegrationsPreferenceCategory; import app.revanced.integrations.tiktok.settings.preference.categories.SimSpoofPreferenceCategory; +import org.jetbrains.annotations.NotNull; /** * Preference fragment for ReVanced settings @@ -13,6 +17,21 @@ import app.revanced.integrations.tiktok.settings.preference.categories.SimSpoofP @SuppressWarnings("deprecation") public class ReVancedPreferenceFragment extends AbstractPreferenceFragment { + @Override + protected void syncSettingWithPreference(@NonNull @NotNull Preference pref, + @NonNull @NotNull Setting setting, + boolean applySettingToPreference) { + if (pref instanceof RangeValuePreference) { + RangeValuePreference rangeValuePref = (RangeValuePreference) pref; + Setting.privateSetValueFromString(setting, rangeValuePref.getValue()); + } else if (pref instanceof DownloadPathPreference) { + DownloadPathPreference downloadPathPref = (DownloadPathPreference) pref; + Setting.privateSetValueFromString(setting, downloadPathPref.getValue()); + } else { + super.syncSettingWithPreference(pref, setting, applySettingToPreference); + } + } + @Override protected void initialize() { final var context = getContext(); From 74b6ab871240549828f7c52052d6fd4f942413d0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 16 Mar 2024 15:57:16 +0000 Subject: [PATCH 23/40] chore(release): 1.5.0-dev.6 [skip ci] # [1.5.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.5...v1.5.0-dev.6) (2024-03-16) ### Bug Fixes * Handle custom preferences ([#586](https://github.com/ReVanced/revanced-integrations/issues/586)) ([ad477e4](https://github.com/ReVanced/revanced-integrations/commit/ad477e4859ef69beda297f7a2a6c29a918077628)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a726beb..d16e634f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.5.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.5...v1.5.0-dev.6) (2024-03-16) + + +### Bug Fixes + +* Handle custom preferences ([#586](https://github.com/ReVanced/revanced-integrations/issues/586)) ([ad477e4](https://github.com/ReVanced/revanced-integrations/commit/ad477e4859ef69beda297f7a2a6c29a918077628)) + # [1.5.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.4...v1.5.0-dev.5) (2024-03-15) diff --git a/gradle.properties b/gradle.properties index aae07e14..0776fa0f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.5.0-dev.5 +version = 1.5.0-dev.6 From 9ac2d634897d961eba1b704f2722ea757bb83e0a Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:21:16 +0400 Subject: [PATCH 24/40] fix(TikTok): Hook application context earlier to prevent crash (#588) --- .../revanced/integrations/shared/Logger.java | 31 +++++++++------ .../revanced/integrations/shared/Utils.java | 20 +++++++--- .../tiktok/spoof/sim/SpoofSimPatch.java | 39 +++++++++++-------- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/shared/Logger.java b/app/src/main/java/app/revanced/integrations/shared/Logger.java index f8c5c87e..25885050 100644 --- a/app/src/main/java/app/revanced/integrations/shared/Logger.java +++ b/app/src/main/java/app/revanced/integrations/shared/Logger.java @@ -5,14 +5,15 @@ import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG_STACK import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR; import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import app.revanced.integrations.shared.settings.BaseSettings; - import java.io.PrintWriter; import java.io.StringWriter; +import app.revanced.integrations.shared.settings.BaseSettings; + public class Logger { /** @@ -24,7 +25,7 @@ public class Logger { /** * @return For outer classes, this returns {@link Class#getSimpleName()}. - * For inner, static, or anonymous classes, this returns the simple name of the enclosing class.
+ * For static, inner, or anonymous classes, this returns the simple name of the enclosing class. *
* For example, each of these classes return 'SomethingView': * @@ -38,13 +39,13 @@ public class Logger { String fullClassName = selfClass.getName(); final int dollarSignIndex = fullClassName.indexOf('$'); - if (dollarSignIndex == -1) { - return selfClass.getSimpleName(); // already an outer class + if (dollarSignIndex < 0) { + return selfClass.getSimpleName(); // Already an outer class. } - // class is inner, static, or anonymous - // parse the simple name full name - // a class with no package returns index of -1, but incrementing gives index zero which is correct + // Class is inner, static, or anonymous. + // Parse the simple name full name. + // A class with no package returns index of -1, but incrementing gives index zero which is correct. final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1; return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex); } @@ -137,11 +138,19 @@ public class Logger { } /** - * Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#context} may not be initialized. - * Always logs even if Debugging is not enabled. + * Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized. * Normally this method should not be used. */ - public static void initializationError(@NonNull Class callingClass, @NonNull String message, @Nullable Exception ex) { + public static void initializationInfo(@NonNull Class callingClass, @NonNull String message) { + Log.i(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message); + } + + /** + * Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized. + * Normally this method should not be used. + */ + public static void initializationException(@NonNull Class callingClass, @NonNull String message, + @Nullable Exception ex) { Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex); } diff --git a/app/src/main/java/app/revanced/integrations/shared/Utils.java b/app/src/main/java/app/revanced/integrations/shared/Utils.java index f92c9cfd..a7fbfcbc 100644 --- a/app/src/main/java/app/revanced/integrations/shared/Utils.java +++ b/app/src/main/java/app/revanced/integrations/shared/Utils.java @@ -54,13 +54,14 @@ public class Utils { try { final var packageName = Objects.requireNonNull(getContext()).getPackageName(); + PackageManager packageManager = context.getPackageManager(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) - packageInfo = context.getPackageManager().getPackageInfo( + packageInfo = packageManager.getPackageInfo( packageName, PackageManager.PackageInfoFlags.of(0) ); else - packageInfo = context.getPackageManager().getPackageInfo( + packageInfo = packageManager.getPackageInfo( packageName, 0 ); @@ -228,14 +229,23 @@ public class Utils { public static Context getContext() { if (context == null) { - Logger.initializationError(Utils.class, "Context is null, returning null!", null); + Logger.initializationException(Utils.class, "Context is null, returning null!", null); } return context; } public static void setContext(Context appContext) { context = appContext; - Logger.printDebug(() -> "Set context: " + appContext); // Cannot log before context is set. + // In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies. + // Calling the regular printDebug method here can cause a Settings context null pointer exception, + // even though the context is already set before the call. + // + // The initialization logger methods do not directly or indirectly + // reference the Context or any Settings and are unaffected by this problem. + // + // Info level also helps debug if a patch hook is called before + // the context is set since debug logging is off by default. + Logger.initializationInfo(Utils.class, "Set context: " + appContext); } public static void setClipboard(@NonNull String text) { @@ -280,7 +290,7 @@ public class Utils { Objects.requireNonNull(messageToToast); runOnMainThreadNowOrLater(() -> { if (context == null) { - Logger.initializationError(Utils.class, "Cannot show toast (context is null): " + messageToToast, null); + Logger.initializationException(Utils.class, "Cannot show toast (context is null): " + messageToToast, null); } else { Logger.printDebug(() -> "Showing toast: " + messageToToast); Toast.makeText(context, messageToToast, toastDuration).show(); diff --git a/app/src/main/java/app/revanced/integrations/tiktok/spoof/sim/SpoofSimPatch.java b/app/src/main/java/app/revanced/integrations/tiktok/spoof/sim/SpoofSimPatch.java index 31145347..264bd893 100644 --- a/app/src/main/java/app/revanced/integrations/tiktok/spoof/sim/SpoofSimPatch.java +++ b/app/src/main/java/app/revanced/integrations/tiktok/spoof/sim/SpoofSimPatch.java @@ -1,32 +1,37 @@ package app.revanced.integrations.tiktok.spoof.sim; +import app.revanced.integrations.shared.Logger; import app.revanced.integrations.tiktok.settings.Settings; @SuppressWarnings("unused") public class SpoofSimPatch { - public static boolean isEnable() { - return Settings.SIM_SPOOF.get(); - } - public static String getCountryIso(String value) { - if (isEnable()) { - return Settings.SIM_SPOOF_ISO.get(); - } else { - return value; - } + private static final Boolean ENABLED = Settings.SIM_SPOOF.get(); + + public static String getCountryIso(String value) { + if (ENABLED) { + String iso = Settings.SIM_SPOOF_ISO.get(); + Logger.printDebug(() -> "Spoofing sim ISO from: " + value + " to: " + iso); + return iso; + } + return value; } + public static String getOperator(String value) { - if (isEnable()) { - return Settings.SIMSPOOF_MCCMNC.get(); - } else { - return value; + if (ENABLED) { + String mcc_mnc = Settings.SIMSPOOF_MCCMNC.get(); + Logger.printDebug(() -> "Spoofing sim MCC-MNC from: " + value + " to: " + mcc_mnc); + return mcc_mnc; } + return value; } + public static String getOperatorName(String value) { - if (isEnable()) { - return Settings.SIMSPOOF_OP_NAME.get(); - } else { - return value; + if (ENABLED) { + String operator = Settings.SIMSPOOF_OP_NAME.get(); + Logger.printDebug(() -> "Spoofing sim operator from: " + value + " to: " + operator); + return operator; } + return value; } } From f47495ccd84b259dfa6c7ab65ae9810f2090d024 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 18 Mar 2024 00:24:14 +0000 Subject: [PATCH 25/40] chore(release): 1.5.0-dev.7 [skip ci] # [1.5.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.6...v1.5.0-dev.7) (2024-03-18) ### Bug Fixes * **TikTok:** Hook application context earlier to prevent crash ([#588](https://github.com/ReVanced/revanced-integrations/issues/588)) ([9ac2d63](https://github.com/ReVanced/revanced-integrations/commit/9ac2d634897d961eba1b704f2722ea757bb83e0a)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d16e634f..ff243e6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.5.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.6...v1.5.0-dev.7) (2024-03-18) + + +### Bug Fixes + +* **TikTok:** Hook application context earlier to prevent crash ([#588](https://github.com/ReVanced/revanced-integrations/issues/588)) ([9ac2d63](https://github.com/ReVanced/revanced-integrations/commit/9ac2d634897d961eba1b704f2722ea757bb83e0a)) + # [1.5.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.5...v1.5.0-dev.6) (2024-03-16) diff --git a/gradle.properties b/gradle.properties index 0776fa0f..55e7f1ae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.5.0-dev.6 +version = 1.5.0-dev.7 From 4ec955fd0133643826e47be7089fbfa07fd9a089 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 25 Mar 2024 20:13:06 +0400 Subject: [PATCH 26/40] fix(YouTube - Hide ads): Prevent app crash if hiding fullscreen ads is not possible (#590) --- .../youtube/patches/components/AdsFilter.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/AdsFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/AdsFilter.java index c122cd91..0c2233a4 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/AdsFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/AdsFilter.java @@ -1,5 +1,7 @@ package app.revanced.integrations.youtube.patches.components; +import static app.revanced.integrations.shared.StringRef.str; + import android.app.Instrumentation; import android.view.KeyEvent; import android.view.View; @@ -170,7 +172,24 @@ public final class AdsFilter extends Filter { Utils.runOnMainThreadDelayed(() -> { // Must run off main thread (Odd, but whatever). - Utils.runOnBackgroundThread(() -> instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK)); + Utils.runOnBackgroundThread(() -> { + try { + instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); + } catch (Exception ex) { + // Injecting user events on Android 10+ requires the manifest to include + // INJECT_EVENTS, and it's usage is heavily restricted + // and requires the user to manually approve the permission in the device settings. + // + // And no matter what, permissions cannot be added for root installations + // as manifest changes are ignored for mount installations. + // + // Instead, catch the SecurityException and turn off hide full screen ads + // since this functionality does not work for these devices. + Logger.printInfo(() -> "Could not inject back button event", ex); + Settings.HIDE_FULLSCREEN_ADS.save(false); + Utils.showToastLong(str("revanced_hide_fullscreen_ads_feature_not_available_toast")); + } + }); }, 1000); } } From 32a14efe6f107c10b72e01a1342849c607293624 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 25 Mar 2024 16:16:13 +0000 Subject: [PATCH 27/40] chore(release): 1.5.0-dev.8 [skip ci] # [1.5.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.7...v1.5.0-dev.8) (2024-03-25) ### Bug Fixes * **YouTube - Hide ads:** Prevent app crash if hiding fullscreen ads is not possible ([#590](https://github.com/ReVanced/revanced-integrations/issues/590)) ([4ec955f](https://github.com/ReVanced/revanced-integrations/commit/4ec955fd0133643826e47be7089fbfa07fd9a089)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff243e6f..7abded65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.5.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.7...v1.5.0-dev.8) (2024-03-25) + + +### Bug Fixes + +* **YouTube - Hide ads:** Prevent app crash if hiding fullscreen ads is not possible ([#590](https://github.com/ReVanced/revanced-integrations/issues/590)) ([4ec955f](https://github.com/ReVanced/revanced-integrations/commit/4ec955fd0133643826e47be7089fbfa07fd9a089)) + # [1.5.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.6...v1.5.0-dev.7) (2024-03-18) diff --git a/gradle.properties b/gradle.properties index 55e7f1ae..582b936e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.5.0-dev.7 +version = 1.5.0-dev.8 From bed8f9f640daadb83406d53a1418c118850d62ef Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 25 Mar 2024 21:11:36 +0100 Subject: [PATCH 28/40] chore: Remove unnecessary consumer parameter (#591) The query parameter was introduced for future use. It turns out that it is unnecessary and can be removed therefor. --- .../patches/announcements/AnnouncementsPatch.java | 14 +------------- .../requests/AnnouncementsRoutes.java | 2 +- .../integrations/youtube/settings/Settings.java | 5 ++++- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/AnnouncementsPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/AnnouncementsPatch.java index 8de878a7..865b13cf 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/AnnouncementsPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/AnnouncementsPatch.java @@ -18,7 +18,6 @@ import java.net.HttpURLConnection; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Locale; -import java.util.UUID; import static android.text.Html.FROM_HTML_MODE_COMPACT; import static app.revanced.integrations.shared.StringRef.str; @@ -26,8 +25,6 @@ import static app.revanced.integrations.youtube.patches.announcements.requests.A @SuppressWarnings("unused") public final class AnnouncementsPatch { - private final static String CONSUMER = getOrSetConsumer(); - private AnnouncementsPatch() { } @@ -41,7 +38,7 @@ public final class AnnouncementsPatch { Utils.runOnBackgroundThread(() -> { try { HttpURLConnection connection = AnnouncementsRoutes.getAnnouncementsConnectionFromRoute( - GET_LATEST_ANNOUNCEMENT, CONSUMER, Locale.getDefault().toLanguageTag()); + GET_LATEST_ANNOUNCEMENT, Locale.getDefault().toLanguageTag()); Logger.printDebug(() -> "Get latest announcement route connection url: " + connection.getURL()); @@ -139,15 +136,6 @@ public final class AnnouncementsPatch { }); } - private static String getOrSetConsumer() { - final var consumer = Settings.ANNOUNCEMENT_CONSUMER.get(); - if (!consumer.isEmpty()) return consumer; - - final var uuid = UUID.randomUUID().toString(); - Settings.ANNOUNCEMENT_CONSUMER.save(uuid); - return uuid; - } - // TODO: Use better icons. private enum Level { INFO(android.R.drawable.ic_dialog_info), diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/requests/AnnouncementsRoutes.java b/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/requests/AnnouncementsRoutes.java index 6fca530c..59273c2b 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/requests/AnnouncementsRoutes.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/announcements/requests/AnnouncementsRoutes.java @@ -14,7 +14,7 @@ public class AnnouncementsRoutes { /** * 'language' parameter is IETF format (for USA it would be 'en-us'). */ - public static final Route GET_LATEST_ANNOUNCEMENT = new Route(GET, "/announcements/youtube/latest?consumer={consumer}&language={language}"); + public static final Route GET_LATEST_ANNOUNCEMENT = new Route(GET, "/announcements/youtube/latest?language={language}"); private AnnouncementsRoutes() { } diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index 928af8c2..8d1b83dd 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -196,7 +196,6 @@ public class Settings extends BaseSettings { public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true); public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE); public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE); - public static final StringSetting ANNOUNCEMENT_CONSUMER = new StringSetting("revanced_announcement_consumer", "", false, false); @Deprecated public static final StringSetting DEPRECATED_ANNOUNCEMENT_LAST_HASH = new StringSetting("revanced_announcement_last_hash", ""); public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1); @@ -346,6 +345,10 @@ public class Settings extends BaseSettings { } + // Remove any previously saved announcement consumer (a random generated string). + Setting.preferences.saveString("revanced_announcement_consumer", null); + + // endregion } } From b945e2f44b1a62326e6d45345c1467668d803f53 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 26 Mar 2024 01:36:13 +0100 Subject: [PATCH 29/40] fix(YouTube - Hide layout components): Correctly hide Join button --- .../components/LayoutComponentsFilter.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/LayoutComponentsFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/LayoutComponentsFilter.java index a260c04e..def0c6d4 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/LayoutComponentsFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/LayoutComponentsFilter.java @@ -29,6 +29,8 @@ public final class LayoutComponentsFilter extends Filter { private final StringFilterGroup expandableMetadata; private final ByteArrayFilterGroup searchResultRecommendations; private final StringFilterGroup searchResultVideo; + private final StringFilterGroup compactChannelBarInner; + private final ByteArrayFilterGroup joinMembership; static { mixPlaylistsExceptions.addPatterns( @@ -194,9 +196,14 @@ public final class LayoutComponentsFilter extends Filter { "set_reminder_button" ); - final var joinMembership = new StringFilterGroup( + compactChannelBarInner = new StringFilterGroup( Settings.HIDE_JOIN_MEMBERSHIP_BUTTON, - "compact_sponsor_button" + "compact_channel_bar_inner" + ); + + joinMembership = new ByteArrayFilterGroup( + Settings.HIDE_JOIN_MEMBERSHIP_BUTTON, + "Join this channel" ); final var channelWatermark = new StringFilterGroup( @@ -233,7 +240,7 @@ public final class LayoutComponentsFilter extends Filter { quickActions, relatedVideos, compactBanner, - joinMembership, + compactChannelBarInner, medicalPanel, videoQualityMenuFooter, infoPanel, @@ -258,6 +265,12 @@ public final class LayoutComponentsFilter extends Filter { } } + if (matchedGroup == compactChannelBarInner) { + if (joinMembership.check(protobufBufferArray).isFiltered()){ + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); + } + } + // The groups are excluded from the filter due to the exceptions list below. // Filter them separately here. if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) From 2a08e5a98e9e9a00bb306313ff487d67c042a92e Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 26 Mar 2024 01:50:40 +0100 Subject: [PATCH 30/40] feat(YouTube - Hide Shorts components): Hide like and dislike buttons --- .../patches/components/ShortsFilter.java | 30 ++++++++++++++----- .../youtube/settings/Settings.java | 2 ++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java index c0c8d8c5..c3f4c77e 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java @@ -27,7 +27,7 @@ public final class ShortsFilter extends Filter { private final StringFilterGroup infoPanel; private final StringFilterGroup shelfHeader; - private final StringFilterGroup videoActionButton; + private final StringFilterGroup actionBar; private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList(); public ShortsFilter() { @@ -94,15 +94,25 @@ public final class ShortsFilter extends Filter { "shorts_info_panel_overview" ); - videoActionButton = new StringFilterGroup( + actionBar = new StringFilterGroup( null, - "ContainerType|shorts_video_action_button" + "shorts_action_bar" ); addPathCallbacks( shortsCompactFeedVideoPath, joinButton, subscribeButton, subscribeButtonPaused, - channelBar, soundButton, infoPanel, videoActionButton + channelBar, soundButton, infoPanel, actionBar + ); + + var shortsLikeButton = new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_LIKE_BUTTON, + "shorts_like_button" + ); + + var shortsDislikeButton = new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_DISLIKE_BUTTON, + "shorts_dislike_button" ); var shortsCommentButton = new ByteArrayFilterGroup( @@ -120,7 +130,13 @@ public final class ShortsFilter extends Filter { "reel_remix_button" ); - videoActionButtonGroupList.addAll(shortsCommentButton, shortsShareButton, shortsRemixButton); + videoActionButtonGroupList.addAll( + shortsLikeButton, + shortsDislikeButton, + shortsCommentButton, + shortsShareButton, + shortsRemixButton + ); } @Override @@ -141,8 +157,8 @@ public final class ShortsFilter extends Filter { return false; } - // Video action buttons (comment, share, remix) have the same path. - if (matchedGroup == videoActionButton) { + // Video action buttons (like, dislike, comment, share, remix) have the same path. + if (matchedGroup == actionBar) { if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered( identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex ); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index 8d1b83dd..b19ccd4c 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -141,6 +141,8 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_shorts_subscribe_button", TRUE); public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED = new BooleanSetting("revanced_hide_shorts_subscribe_button_paused", FALSE); public static final BooleanSetting HIDE_SHORTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_shorts_thanks_button", TRUE); + public static final BooleanSetting HIDE_SHORTS_LIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_like_button", FALSE); + public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE); public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE); public static final BooleanSetting HIDE_SHORTS_REMIX_BUTTON = new BooleanSetting("revanced_hide_shorts_remix_button", TRUE); public static final BooleanSetting HIDE_SHORTS_SHARE_BUTTON = new BooleanSetting("revanced_hide_shorts_share_button", FALSE); From 59165de801a5481fa4055dcf1797fe84dce235c0 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 26 Mar 2024 01:51:26 +0100 Subject: [PATCH 31/40] feat(YouTube - Hide Shorts components): Hide title and full video link label --- .../patches/components/ShortsFilter.java | 17 ++++++++++++++++- .../integrations/youtube/settings/Settings.java | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java index c3f4c77e..27f880c4 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java @@ -21,6 +21,8 @@ public final class ShortsFilter extends Filter { private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer; private final StringFilterGroup channelBar; + private final StringFilterGroup fullVideoLinkLabel; + private final StringFilterGroup videoTitle; private final StringFilterGroup subscribeButton; private final StringFilterGroup subscribeButtonPaused; private final StringFilterGroup soundButton; @@ -84,6 +86,16 @@ public final class ShortsFilter extends Filter { REEL_CHANNEL_BAR_PATH ); + fullVideoLinkLabel = new StringFilterGroup( + Settings.HIDE_SHORTS_FULL_VIDEO_LINK_LABEL, + "reel_multi_format_link" + ); + + videoTitle = new StringFilterGroup( + Settings.HIDE_SHORTS_VIDEO_TITLE, + "shorts_video_title_item" + ); + soundButton = new StringFilterGroup( Settings.HIDE_SHORTS_SOUND_BUTTON, "reel_pivot_button" @@ -102,7 +114,8 @@ public final class ShortsFilter extends Filter { addPathCallbacks( shortsCompactFeedVideoPath, joinButton, subscribeButton, subscribeButtonPaused, - channelBar, soundButton, infoPanel, actionBar + channelBar, fullVideoLinkLabel, videoTitle, soundButton, + infoPanel, actionBar ); var shortsLikeButton = new ByteArrayFilterGroup( @@ -147,6 +160,8 @@ public final class ShortsFilter extends Filter { if (matchedGroup == soundButton || matchedGroup == infoPanel || matchedGroup == channelBar || + matchedGroup == fullVideoLinkLabel || + matchedGroup == videoTitle || matchedGroup == subscribeButtonPaused ) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index b19ccd4c..50e42358 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -149,6 +149,8 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_SHORTS_INFO_PANEL = new BooleanSetting("revanced_hide_shorts_info_panel", TRUE); public static final BooleanSetting HIDE_SHORTS_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_sound_button", FALSE); public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE); + public static final BooleanSetting HIDE_SHORTS_VIDEO_TITLE = new BooleanSetting("revanced_hide_shorts_video_title", FALSE); + public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE); public static final BooleanSetting HIDE_SHORTS_NAVIGATION_BAR = new BooleanSetting("revanced_hide_shorts_navigation_bar", TRUE, true); // Seekbar From b7a8995f798e386ee1d9ab5bbd857c1736cc5a29 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 26 Mar 2024 01:52:41 +0100 Subject: [PATCH 32/40] fix(YouTube - Hide Shorts components): Correctly hide join button --- .../components/LayoutComponentsFilter.java | 33 +++++++++++++------ .../patches/components/ShortsFilter.java | 7 ++-- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/LayoutComponentsFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/LayoutComponentsFilter.java index def0c6d4..fb1cd0fb 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/LayoutComponentsFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/LayoutComponentsFilter.java @@ -30,7 +30,8 @@ public final class LayoutComponentsFilter extends Filter { private final ByteArrayFilterGroup searchResultRecommendations; private final StringFilterGroup searchResultVideo; private final StringFilterGroup compactChannelBarInner; - private final ByteArrayFilterGroup joinMembership; + private final StringFilterGroup compactChannelBarInnerButton; + private final ByteArrayFilterGroup joinMembershipButton; static { mixPlaylistsExceptions.addPatterns( @@ -39,6 +40,7 @@ public final class LayoutComponentsFilter extends Filter { ); } + @RequiresApi(api = Build.VERSION_CODES.N) public LayoutComponentsFilter() { exceptions.addPatterns( @@ -201,9 +203,14 @@ public final class LayoutComponentsFilter extends Filter { "compact_channel_bar_inner" ); - joinMembership = new ByteArrayFilterGroup( - Settings.HIDE_JOIN_MEMBERSHIP_BUTTON, - "Join this channel" + compactChannelBarInnerButton = new StringFilterGroup( + null, + "|button.eml|" + ); + + joinMembershipButton = new ByteArrayFilterGroup( + null, + "sponsorships" ); final var channelWatermark = new StringFilterGroup( @@ -265,12 +272,6 @@ public final class LayoutComponentsFilter extends Filter { } } - if (matchedGroup == compactChannelBarInner) { - if (joinMembership.check(protobufBufferArray).isFiltered()){ - return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); - } - } - // The groups are excluded from the filter due to the exceptions list below. // Filter them separately here. if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) @@ -278,6 +279,18 @@ public final class LayoutComponentsFilter extends Filter { if (exceptions.matches(path)) return false; // Exceptions are not filtered. + if (matchedGroup == compactChannelBarInner) { + if (compactChannelBarInnerButton.check(path).isFiltered()) { + // The filter may be broad, but in the context of a compactChannelBarInnerButton, + // it's safe to assume that the button is the only thing that should be hidden. + if (joinMembershipButton.check(protobufBufferArray).isFiltered()) { + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); + } + } + + return false; + } + // TODO: This also hides the feed Shorts shelf header if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false; diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java index 27f880c4..0f425641 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java @@ -27,6 +27,7 @@ public final class ShortsFilter extends Filter { private final StringFilterGroup subscribeButtonPaused; private final StringFilterGroup soundButton; private final StringFilterGroup infoPanel; + private final StringFilterGroup joinButton; private final StringFilterGroup shelfHeader; private final StringFilterGroup actionBar; @@ -66,7 +67,7 @@ public final class ShortsFilter extends Filter { "/frame0.jpg"); // Shorts player components. - var joinButton = new StringFilterGroup( + joinButton = new StringFilterGroup( Settings.HIDE_SHORTS_JOIN_BUTTON, "sponsor_button" ); @@ -182,7 +183,9 @@ public final class ShortsFilter extends Filter { // Filter other path groups from pathFilterGroupList, only when reelChannelBar is visible // to avoid false positives. - if (matchedGroup == subscribeButton) { + if (matchedGroup == subscribeButton || + matchedGroup == joinButton + ) { if (path.startsWith(REEL_CHANNEL_BAR_PATH)) return super.isFiltered( identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex ); From 46d8ef6f88bd4c912a45357541291af38b5fc81f Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 26 Mar 2024 02:17:42 +0100 Subject: [PATCH 33/40] feat(YouTube - Hide Shorts components): Hide sound metadata label --- .../youtube/patches/components/ShortsFilter.java | 11 +++++++++-- .../integrations/youtube/settings/Settings.java | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java index 0f425641..8bbcae7f 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java @@ -23,6 +23,7 @@ public final class ShortsFilter extends Filter { private final StringFilterGroup channelBar; private final StringFilterGroup fullVideoLinkLabel; private final StringFilterGroup videoTitle; + private final StringFilterGroup reelSoundMetadata; private final StringFilterGroup subscribeButton; private final StringFilterGroup subscribeButtonPaused; private final StringFilterGroup soundButton; @@ -97,6 +98,11 @@ public final class ShortsFilter extends Filter { "shorts_video_title_item" ); + reelSoundMetadata = new StringFilterGroup( + Settings.HIDE_SHORTS_SOUND_METADATA_LABEL, + "reel_sound_metadata" + ); + soundButton = new StringFilterGroup( Settings.HIDE_SHORTS_SOUND_BUTTON, "reel_pivot_button" @@ -115,8 +121,8 @@ public final class ShortsFilter extends Filter { addPathCallbacks( shortsCompactFeedVideoPath, joinButton, subscribeButton, subscribeButtonPaused, - channelBar, fullVideoLinkLabel, videoTitle, soundButton, - infoPanel, actionBar + channelBar, fullVideoLinkLabel, videoTitle, reelSoundMetadata, + soundButton, infoPanel, actionBar ); var shortsLikeButton = new ByteArrayFilterGroup( @@ -163,6 +169,7 @@ public final class ShortsFilter extends Filter { matchedGroup == channelBar || matchedGroup == fullVideoLinkLabel || matchedGroup == videoTitle || + matchedGroup == reelSoundMetadata || matchedGroup == subscribeButtonPaused ) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index 50e42358..19da334d 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -150,6 +150,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_SHORTS_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_sound_button", FALSE); public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE); public static final BooleanSetting HIDE_SHORTS_VIDEO_TITLE = new BooleanSetting("revanced_hide_shorts_video_title", FALSE); + public static final BooleanSetting HIDE_SHORTS_SOUND_METADATA_LABEL = new BooleanSetting("revanced_hide_shorts_sound_metadata_label", FALSE); public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE); public static final BooleanSetting HIDE_SHORTS_NAVIGATION_BAR = new BooleanSetting("revanced_hide_shorts_navigation_bar", TRUE, true); From 96a1e4680d23be7154bb83290b1887fcd1a22f53 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 26 Mar 2024 04:47:21 +0100 Subject: [PATCH 34/40] fix: Check index of pattern in string instead of the other way around --- .../youtube/patches/components/LithoFilterPatch.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/LithoFilterPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/LithoFilterPatch.java index f77a9ac5..51c36493 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/LithoFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/LithoFilterPatch.java @@ -1,7 +1,6 @@ package app.revanced.integrations.youtube.patches.components; import android.os.Build; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -15,7 +14,6 @@ import java.util.Spliterator; import java.util.function.Consumer; import app.revanced.integrations.shared.Logger; -import app.revanced.integrations.shared.Utils; import app.revanced.integrations.shared.settings.BooleanSetting; import app.revanced.integrations.shared.settings.BaseSettings; import app.revanced.integrations.youtube.ByteTrieSearch; @@ -124,7 +122,7 @@ class StringFilterGroup extends FilterGroup { if (isEnabled()) { for (String pattern : filters) { if (!string.isEmpty()) { - final int indexOf = pattern.indexOf(string); + final int indexOf = string.indexOf(pattern); if (indexOf >= 0) { matchedIndex = indexOf; matchedLength = pattern.length(); From 6e947e24c2ac36e1bddcd25412870a36aa6c85c8 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 26 Mar 2024 22:29:06 +0100 Subject: [PATCH 35/40] fix(YouTube): Fix video playback by switching to ReVanced GmsCore vendor (#589) --- .../youtube/patches/GmsCoreSupport.java | 11 ++++------- .../patches/spoof/SpoofSignaturePatch.java | 17 ++++++++--------- .../patches/spoof/StoryboardRenderer.java | 1 + .../patches/spoof/requests/PlayerRoutes.java | 1 + .../requests/StoryboardRendererRequester.java | 1 + 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/GmsCoreSupport.java b/app/src/main/java/app/revanced/integrations/youtube/patches/GmsCoreSupport.java index ea1273fe..ddf4786e 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/GmsCoreSupport.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/GmsCoreSupport.java @@ -1,20 +1,18 @@ package app.revanced.integrations.youtube.patches; -import static app.revanced.integrations.shared.StringRef.str; - import android.app.SearchManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; - import androidx.annotation.RequiresApi; +import app.revanced.integrations.shared.Logger; +import app.revanced.integrations.shared.Utils; import java.util.Objects; -import app.revanced.integrations.shared.Logger; -import app.revanced.integrations.shared.Utils; +import static app.revanced.integrations.shared.StringRef.str; /** * @noinspection unused @@ -61,9 +59,8 @@ public class GmsCoreSupport { private static String getGmsCoreDownloadLink() { final var vendor = getGmsCoreVendor(); + //noinspection SwitchStatementWithTooFewBranches switch (vendor) { - case "com.mgoogle": - return "https://github.com/TeamVanced/VancedMicroG/releases/latest"; case "app.revanced": return "https://github.com/revanced/gmscore/releases/latest"; default: diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch.java index 531d2b81..6573467a 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch.java @@ -1,26 +1,25 @@ package app.revanced.integrations.youtube.patches.spoof; -import static app.revanced.integrations.youtube.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer; -import static app.revanced.integrations.shared.Utils.containsAny; - import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; - import androidx.annotation.Nullable; +import app.revanced.integrations.shared.Logger; +import app.revanced.integrations.shared.Utils; +import app.revanced.integrations.youtube.patches.VideoInformation; +import app.revanced.integrations.youtube.settings.Settings; +import app.revanced.integrations.youtube.shared.PlayerType; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import app.revanced.integrations.youtube.patches.VideoInformation; -import app.revanced.integrations.youtube.settings.Settings; -import app.revanced.integrations.youtube.shared.PlayerType; -import app.revanced.integrations.shared.Logger; -import app.revanced.integrations.shared.Utils; +import static app.revanced.integrations.shared.Utils.containsAny; +import static app.revanced.integrations.youtube.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer; /** @noinspection unused*/ +@Deprecated public class SpoofSignaturePatch { /** * Parameter (also used by diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/StoryboardRenderer.java b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/StoryboardRenderer.java index f2f69db7..8e337297 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/StoryboardRenderer.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/StoryboardRenderer.java @@ -4,6 +4,7 @@ import androidx.annotation.Nullable; import org.jetbrains.annotations.NotNull; +@Deprecated public final class StoryboardRenderer { @Nullable private final String spec; diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/PlayerRoutes.java b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/PlayerRoutes.java index c8f7741c..67e69502 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/PlayerRoutes.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/PlayerRoutes.java @@ -10,6 +10,7 @@ import org.json.JSONObject; import java.io.IOException; import java.net.HttpURLConnection; +@Deprecated final class PlayerRoutes { private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/"; static final Route.CompiledRoute GET_STORYBOARD_SPEC_RENDERER = new Route( diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/StoryboardRendererRequester.java b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/StoryboardRendererRequester.java index aeb7fe25..31d8c3ef 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/StoryboardRendererRequester.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/spoof/requests/StoryboardRendererRequester.java @@ -19,6 +19,7 @@ import java.util.Objects; import static app.revanced.integrations.shared.StringRef.str; import static app.revanced.integrations.youtube.patches.spoof.requests.PlayerRoutes.*; +@Deprecated public class StoryboardRendererRequester { /** From 0cbad9820577c476f1f29b6ac77611b38afbb950 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:26:26 +0400 Subject: [PATCH 36/40] feat(YouTube - Hide layout components): Filter home/search results by keywords (#584) Co-authored-by: oSumAtrIX --- .../revanced/integrations/shared/Utils.java | 19 +- .../integrations/youtube/ByteTrieSearch.java | 22 +- .../youtube/StringTrieSearch.java | 18 +- .../integrations/youtube/TrieSearch.java | 56 ++-- .../patches/NavigationButtonsPatch.java | 47 +-- .../patches/components/CustomFilter.java | 20 +- .../components/KeywordContentFilter.java | 284 ++++++++++++++++++ .../patches/components/LithoFilterPatch.java | 3 +- .../youtube/settings/LicenseActivityHook.java | 6 +- .../youtube/settings/Settings.java | 9 + .../youtube/shared/NavigationBar.java | 184 ++++++++++++ .../integrations/youtube/shared/PlayerType.kt | 4 + 12 files changed, 563 insertions(+), 109 deletions(-) create mode 100644 app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java create mode 100644 app/src/main/java/app/revanced/integrations/youtube/shared/NavigationBar.java diff --git a/app/src/main/java/app/revanced/integrations/shared/Utils.java b/app/src/main/java/app/revanced/integrations/shared/Utils.java index a7fbfcbc..371100d1 100644 --- a/app/src/main/java/app/revanced/integrations/shared/Utils.java +++ b/app/src/main/java/app/revanced/integrations/shared/Utils.java @@ -196,18 +196,29 @@ public class Utils { return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen")); } + public interface MatchFilter { + boolean matches(T object); + } + /** + * @param searchRecursively If children ViewGroups should also be + * recursively searched using depth first search. * @return The first child view that matches the filter. */ @Nullable - public static T getChildView(@NonNull ViewGroup viewGroup, @NonNull MatchFilter filter) { + public static T getChildView(@NonNull ViewGroup viewGroup, boolean searchRecursively, + @NonNull MatchFilter filter) { for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) { View childAt = viewGroup.getChildAt(i); - //noinspection unchecked if (filter.matches(childAt)) { //noinspection unchecked return (T) childAt; } + // Must do recursive after filter check, in case the filter is looking for a ViewGroup. + if (searchRecursively && childAt instanceof ViewGroup) { + T match = getChildView((ViewGroup) childAt, true, filter); + if (match != null) return match; + } } return null; } @@ -223,10 +234,6 @@ public class Utils { System.exit(0); } - public interface MatchFilter { - boolean matches(T object); - } - public static Context getContext() { if (context == null) { Logger.initializationException(Utils.class, "Context is null, returning null!", null); diff --git a/app/src/main/java/app/revanced/integrations/youtube/ByteTrieSearch.java b/app/src/main/java/app/revanced/integrations/youtube/ByteTrieSearch.java index bc564675..aa2b94b8 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/ByteTrieSearch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/ByteTrieSearch.java @@ -1,5 +1,9 @@ package app.revanced.integrations.youtube; +import androidx.annotation.NonNull; + +import java.nio.charset.StandardCharsets; + public final class ByteTrieSearch extends TrieSearch { private static final class ByteTrieNode extends TrieNode { @@ -24,18 +28,18 @@ public final class ByteTrieSearch extends TrieSearch { } /** - * @return If the pattern is valid to add to this instance. + * Helper method for the common usage of converting Strings to raw UTF-8 bytes. */ - public static boolean isValidPattern(byte[] pattern) { - for (byte b : pattern) { - if (TrieNode.isInvalidRange((char) b)) { - return false; - } + public static byte[][] convertStringsToBytes(String... strings) { + final int length = strings.length; + byte[][] replacement = new byte[length][]; + for (int i = 0; i < length; i++) { + replacement[i] = strings[i].getBytes(StandardCharsets.UTF_8); } - return true; + return replacement; } - public ByteTrieSearch() { - super(new ByteTrieNode()); + public ByteTrieSearch(@NonNull byte[]... patterns) { + super(new ByteTrieNode(), patterns); } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/StringTrieSearch.java b/app/src/main/java/app/revanced/integrations/youtube/StringTrieSearch.java index d2fb7f78..618d9d66 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/StringTrieSearch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/StringTrieSearch.java @@ -1,5 +1,7 @@ package app.revanced.integrations.youtube; +import androidx.annotation.NonNull; + /** * Text pattern searching using a prefix tree (trie). */ @@ -26,19 +28,7 @@ public final class StringTrieSearch extends TrieSearch { } } - /** - * @return If the pattern is valid to add to this instance. - */ - public static boolean isValidPattern(String pattern) { - for (int i = 0, length = pattern.length(); i < length; i++) { - if (TrieNode.isInvalidRange(pattern.charAt(i))) { - return false; - } - } - return true; - } - - public StringTrieSearch() { - super(new StringTrieNode()); + public StringTrieSearch(@NonNull String... patterns) { + super(new StringTrieNode(), patterns); } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/TrieSearch.java b/app/src/main/java/app/revanced/integrations/youtube/TrieSearch.java index 8316597d..1c927cd2 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/TrieSearch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/TrieSearch.java @@ -11,9 +11,6 @@ import java.util.Objects; /** * Searches for a group of different patterns using a trie (prefix tree). * Can significantly speed up searching for multiple patterns. - * - * Currently only supports ASCII non-control characters (letters/numbers/symbols). - * But could be modified to also support UTF-8 unicode. */ public abstract class TrieSearch { @@ -45,14 +42,14 @@ public abstract class TrieSearch { */ private static final class TrieCompressedPath { final T pattern; - final int patternLength; final int patternStartIndex; + final int patternLength; final TriePatternMatchedCallback callback; - TrieCompressedPath(T pattern, int patternLength, int patternStartIndex, TriePatternMatchedCallback callback) { + TrieCompressedPath(T pattern, int patternStartIndex, int patternLength, TriePatternMatchedCallback callback) { this.pattern = pattern; - this.patternLength = patternLength; this.patternStartIndex = patternStartIndex; + this.patternLength = patternLength; this.callback = callback; } boolean matches(TrieNode enclosingNode, // Used only for the get character method. @@ -76,19 +73,10 @@ public abstract class TrieSearch { */ private static final char ROOT_NODE_CHARACTER_VALUE = 0; // ASCII null character. - // Support only ASCII letters/numbers/symbols and filter out all control characters. - private static final char MIN_VALID_CHAR = 32; // Space character. - private static final char MAX_VALID_CHAR = 126; // 127 = delete character. - /** * How much to expand the children array when resizing. */ private static final int CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT = 2; - private static final int CHILDREN_ARRAY_MAX_SIZE = MAX_VALID_CHAR - MIN_VALID_CHAR + 1; - - static boolean isInvalidRange(char character) { - return character < MIN_VALID_CHAR || character > MAX_VALID_CHAR; - } /** * Character this node represents. @@ -144,11 +132,11 @@ public abstract class TrieSearch { /** * @param pattern Pattern to add. - * @param patternLength Length of the pattern. * @param patternIndex Current recursive index of the pattern. + * @param patternLength Length of the pattern. * @param callback Callback, where a value of NULL indicates to always accept a pattern match. */ - private void addPattern(@NonNull T pattern, int patternLength, int patternIndex, + private void addPattern(@NonNull T pattern, int patternIndex, int patternLength, @Nullable TriePatternMatchedCallback callback) { if (patternIndex == patternLength) { // Reached the end of the pattern. if (endOfPatternCallback == null) { @@ -165,16 +153,13 @@ public abstract class TrieSearch { children = new TrieNode[1]; TrieCompressedPath temp = leaf; leaf = null; - addPattern(temp.pattern, temp.patternLength, temp.patternStartIndex, temp.callback); + addPattern(temp.pattern, temp.patternStartIndex, temp.patternLength, temp.callback); // Continue onward and add the parameter pattern. } else if (children == null) { - leaf = new TrieCompressedPath<>(pattern, patternLength, patternIndex, callback); + leaf = new TrieCompressedPath<>(pattern, patternIndex, patternLength, callback); return; } final char character = getCharValue(pattern, patternIndex); - if (isInvalidRange(character)) { - throw new IllegalArgumentException("invalid character at index " + patternIndex + ": " + pattern); - } final int arrayIndex = hashIndexForTableSize(children.length, character); TrieNode child = children[arrayIndex]; if (child == null) { @@ -185,12 +170,11 @@ public abstract class TrieSearch { child = createNode(character); expandChildArray(child); } - child.addPattern(pattern, patternLength, patternIndex + 1, callback); + child.addPattern(pattern, patternIndex + 1, patternLength, callback); } /** * Resizes the children table until all nodes hash to exactly one array index. - * Worse case, this will resize the array to {@link #CHILDREN_ARRAY_MAX_SIZE} elements. */ private void expandChildArray(TrieNode child) { int replacementArraySize = Objects.requireNonNull(children).length; @@ -209,7 +193,6 @@ public abstract class TrieSearch { } } if (collision) { - if (replacementArraySize > CHILDREN_ARRAY_MAX_SIZE) throw new IllegalStateException(); continue; } children = replacement; @@ -232,22 +215,23 @@ public abstract class TrieSearch { /** * This method is static and uses a loop to avoid all recursion. - * This is done for performance since the JVM does not do tail recursion optimization. + * This is done for performance since the JVM does not optimize tail recursion. * * @param startNode Node to start the search from. * @param searchText Text to search for patterns in. - * @param searchTextLength Length of the search text. - * @param searchTextIndex Current recursive search text index. Also, the end index of the current pattern match. + * @param searchTextIndex Start index, inclusive. + * @param searchTextEndIndex End index, exclusive. * @return If any pattern matches, and it's associated callback halted the search. */ - private static boolean matches(final TrieNode startNode, final T searchText, final int searchTextLength, - int searchTextIndex, final Object callbackParameter) { + private static boolean matches(final TrieNode startNode, final T searchText, + int searchTextIndex, final int searchTextEndIndex, + final Object callbackParameter) { TrieNode node = startNode; int currentMatchLength = 0; while (true) { TrieCompressedPath leaf = node.leaf; - if (leaf != null && leaf.matches(node, searchText, searchTextLength, searchTextIndex, callbackParameter)) { + if (leaf != null && leaf.matches(startNode, searchText, searchTextEndIndex, searchTextIndex, callbackParameter)) { return true; // Leaf exists and it matched the search text. } List> endOfPatternCallback = node.endOfPatternCallback; @@ -266,7 +250,7 @@ public abstract class TrieSearch { if (children == null) { return false; // Reached a graph end point and there's no further patterns to search. } - if (searchTextIndex == searchTextLength) { + if (searchTextIndex == searchTextEndIndex) { return false; // Reached end of the search text and found no matches. } @@ -323,8 +307,10 @@ public abstract class TrieSearch { */ private final List patterns = new ArrayList<>(); - TrieSearch(@NonNull TrieNode root) { + @SafeVarargs + TrieSearch(@NonNull TrieNode root, @NonNull T... patterns) { this.root = Objects.requireNonNull(root); + addPatterns(patterns); } @SafeVarargs @@ -355,7 +341,7 @@ public abstract class TrieSearch { if (patternLength == 0) return; // Nothing to match patterns.add(pattern); - root.addPattern(pattern, patternLength, 0, callback); + root.addPattern(pattern, 0, patternLength, callback); } public final boolean matches(@NonNull T textToSearch) { @@ -398,7 +384,7 @@ public abstract class TrieSearch { return false; // No patterns were added. } for (int i = startIndex; i < endIndex; i++) { - if (TrieNode.matches(root, textToSearch, endIndex, i, callbackParameter)) return true; + if (TrieNode.matches(root, textToSearch, i, endIndex, callbackParameter)) return true; } return false; } diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/NavigationButtonsPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/NavigationButtonsPatch.java index c063d418..bd5f6cfb 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/NavigationButtonsPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/NavigationButtonsPatch.java @@ -1,40 +1,41 @@ package app.revanced.integrations.youtube.patches; +import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton; import android.view.View; +import java.util.EnumMap; +import java.util.Map; + import app.revanced.integrations.youtube.settings.Settings; @SuppressWarnings("unused") public final class NavigationButtonsPatch { - public static Enum lastNavigationButton; - public static void hideCreateButton(final View view) { - view.setVisibility(Settings.HIDE_CREATE_BUTTON.get() ? View.GONE : View.VISIBLE); - } + private static final Map shouldHideMap = new EnumMap<>(NavigationButton.class) { + { + put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get()); + put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get()); + put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get()); + } + }; + private static final Boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON + = Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get(); + + /** + * Injection point. + */ public static boolean switchCreateWithNotificationButton() { - return Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get(); + return SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON; } - public static void hideButton(final View buttonView) { - if (lastNavigationButton == null) return; - - for (NavigationButton button : NavigationButton.values()) - if (button.name.equals(lastNavigationButton.name())) - if (button.enabled) buttonView.setVisibility(View.GONE); - } - - private enum NavigationButton { - HOME("PIVOT_HOME", Settings.HIDE_HOME_BUTTON.get()), - SHORTS("TAB_SHORTS", Settings.HIDE_SHORTS_BUTTON.get()), - SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", Settings.HIDE_SUBSCRIPTIONS_BUTTON.get()); - private final boolean enabled; - private final String name; - - NavigationButton(final String name, final boolean enabled) { - this.name = name; - this.enabled = enabled; + /** + * Injection point. + */ + public static void navigationTabCreated(NavigationButton button, View tabView) { + if (Boolean.TRUE.equals(shouldHideMap.get(button))) { + tabView.setVisibility(View.GONE); } } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/CustomFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/CustomFilter.java index f3c59660..3e62d040 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/CustomFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/CustomFilter.java @@ -17,7 +17,6 @@ import java.util.regex.Pattern; import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.Utils; import app.revanced.integrations.youtube.ByteTrieSearch; -import app.revanced.integrations.youtube.StringTrieSearch; import app.revanced.integrations.youtube.settings.Settings; /** @@ -30,10 +29,6 @@ final class CustomFilter extends Filter { Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression)); } - private static void showInvalidCharactersToast(@NonNull String expression) { - Utils.showToastLong(str("revanced_custom_filter_toast_invalid_characters", expression)); - } - private static class CustomFilterGroup extends StringFilterGroup { /** * Optional character for the path that indicates the custom filter path must match the start. @@ -73,7 +68,7 @@ final class CustomFilter extends Filter { Matcher matcher = pattern.matcher(expression); if (!matcher.find()) { showInvalidSyntaxToast(expression); - return null; + continue; } final String mapKey = matcher.group(1); @@ -84,13 +79,7 @@ final class CustomFilter extends Filter { if (path.isBlank() || (hasBufferSymbol && bufferString.isBlank())) { showInvalidSyntaxToast(expression); - return null; - } - if (!StringTrieSearch.isValidPattern(path) - || (hasBufferSymbol && !StringTrieSearch.isValidPattern(bufferString))) { - // Currently only ASCII is allowed. - showInvalidCharactersToast(path); - return null; + continue; } // Use one group object for all expressions with the same path. @@ -149,11 +138,6 @@ final class CustomFilter extends Filter { public CustomFilter() { Collection groups = CustomFilterGroup.parseCustomFilterGroups(); - if (groups == null) { - Settings.CUSTOM_FILTER_STRINGS.resetToDefault(); - Utils.showToastLong(str("revanced_custom_filter_toast_reset")); - groups = Objects.requireNonNull(CustomFilterGroup.parseCustomFilterGroups()); - } if (!groups.isEmpty()) { CustomFilterGroup[] groupsArray = groups.toArray(new CustomFilterGroup[0]); diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java new file mode 100644 index 00000000..6ea91beb --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java @@ -0,0 +1,284 @@ +package app.revanced.integrations.youtube.patches.components; + +import static app.revanced.integrations.shared.StringRef.str; +import static app.revanced.integrations.youtube.ByteTrieSearch.convertStringsToBytes; +import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton; + +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import app.revanced.integrations.shared.Logger; +import app.revanced.integrations.shared.Utils; +import app.revanced.integrations.youtube.ByteTrieSearch; +import app.revanced.integrations.youtube.settings.Settings; +import app.revanced.integrations.youtube.shared.NavigationBar; +import app.revanced.integrations.youtube.shared.PlayerType; + +/** + *
+ * Allows hiding home feed and search results based on keywords and/or channel names.
+ *
+ * Limitations:
+ * - Searching for a keyword phrase will give no search results.
+ *   This is because the buffer for each video contains the text the user searched for, and everything
+ *   will be filtered away (even if that video title/channel does not contain any keywords).
+ * - Filtering a channel name can still show Shorts from that channel in the search results.
+ *   The most common Shorts layouts do not include the channel name, so they will not be filtered.
+ * - Some layout component residue will remain, such as the video chapter previews for some search results.
+ *   These components do not include the video title or channel name, and they
+ *   appear outside the filtered components so they are not caught.
+ * - Keywords are case sensitive, but some casing variation is manually added.
+ *   (ie: "mr beast" automatically filters "Mr Beast" and "MR BEAST").
+ * - Keywords present in the layout or video data cannot be used as filters, otherwise all videos
+ *   will always be hidden.  This patch checks for some words of these words.
+ */
+@SuppressWarnings("unused")
+@RequiresApi(api = Build.VERSION_CODES.N)
+final class KeywordContentFilter extends Filter {
+
+    /**
+     * Minimum keyword/phrase length to prevent excessively broad content filtering.
+     */
+    private static final int MINIMUM_KEYWORD_LENGTH = 3;
+
+    /**
+     * Strings found in the buffer for every videos.
+     * Full strings should be specified, as they are compared using {@link String#contains(CharSequence)}.
+     *
+     * This list does not include every common buffer string, and this can be added/changed as needed.
+     * Words must be entered with the exact casing as found in the buffer.
+     */
+    private static final String[] STRINGS_IN_EVERY_BUFFER = {
+            // Video playback data.
+            "https://i.ytimg.com/vi/", // Thumbnail url.
+            "sddefault.jpg", // More video sizes exist, but for most devices only these 2 are used.
+            "hqdefault.webp",
+            "googlevideo.com/initplayback?source=youtube", // Video url.
+            "ANDROID", // Video url parameter.
+            // Video decoders.
+            "OMX.ffmpeg.vp9.decoder",
+            "OMX.Intel.sw_vd.vp9",
+            "OMX.sprd.av1.decoder",
+            "OMX.MTK.VIDEO.DECODER.SW.VP9",
+            "c2.android.av1.decoder",
+            "c2.mtk.sw.vp9.decoder",
+            // User analytics.
+            "https://ad.doubleclick.net/ddm/activity/",
+            "DEVICE_ADVERTISER_ID_FOR_CONVERSION_TRACKING",
+            // Litho components frequently found in the buffer that belong to the path filter items.
+            "metadata.eml",
+            "thumbnail.eml",
+            "avatar.eml",
+            "overflow_button.eml",
+    };
+
+    /**
+     * Substrings that are always first in the identifier.
+     */
+    private final StringFilterGroup startsWithFilter = new StringFilterGroup(
+            null, // Multiple settings are used and must be individually checked if active.
+            "home_video_with_context.eml",
+            "search_video_with_context.eml",
+            "video_with_context.eml", // Subscription tab videos.
+            "related_video_with_context.eml",
+            "compact_video.eml",
+            "inline_shorts",
+            "shorts_video_cell",
+            "shorts_pivot_item.eml"
+    );
+
+    /**
+     * Substrings that are never at the start of the path.
+     */
+    private final StringFilterGroup containsFilter = new StringFilterGroup(
+            null,
+            "modern_type_shelf_header_content.eml",
+             "shorts_lockup_cell.eml" // Part of 'shorts_shelf_carousel.eml'
+    );
+
+    /**
+     * The last value of {@link Settings#HIDE_KEYWORD_CONTENT_PHRASES}
+     * parsed and loaded into {@link #bufferSearch}.
+     * Allows changing the keywords without restarting the app.
+     */
+    private volatile String lastKeywordPhrasesParsed;
+
+    private volatile ByteTrieSearch bufferSearch;
+
+    /**
+     * Change first letter of the first word to use title case.
+     */
+    private static String titleCaseFirstWordOnly(String sentence) {
+        if (sentence.isEmpty()) {
+            return sentence;
+        }
+        final int firstCodePoint = sentence.codePointAt(0);
+        // In some non English languages title case is different than upper case.
+        return new StringBuilder()
+                .appendCodePoint(Character.toTitleCase(firstCodePoint))
+                .append(sentence, Character.charCount(firstCodePoint), sentence.length())
+                .toString();
+    }
+
+    /**
+     * Uppercase the first letter of each word.
+     */
+    private static String capitalizeAllFirstLetters(String sentence) {
+        if (sentence.isEmpty()) {
+            return sentence;
+        }
+        final int delimiter = ' ';
+        // Use code points and not characters to handle unicode surrogates.
+        int[] codePoints = sentence.codePoints().toArray();
+        boolean capitalizeNext = true;
+        for (int i = 0, length = codePoints.length; i < length; i++) {
+            final int codePoint = codePoints[i];
+            if (codePoint == delimiter) {
+                capitalizeNext = true;
+            } else if (capitalizeNext) {
+                codePoints[i] = Character.toUpperCase(codePoint);
+                capitalizeNext = false;
+            }
+        }
+        return new String(codePoints, 0, codePoints.length);
+    }
+
+    /**
+     * @return If the phrase will will hide all videos. Not an exhaustive check.
+     */
+    private static boolean phrasesWillHideAllVideos(@NonNull String[] phrases) {
+        for (String commonString : STRINGS_IN_EVERY_BUFFER) {
+            if (Utils.containsAny(commonString, phrases)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private synchronized void parseKeywords() { // Must be synchronized since Litho is multi-threaded.
+        String rawKeywords = Settings.HIDE_KEYWORD_CONTENT_PHRASES.get();
+        if (rawKeywords == lastKeywordPhrasesParsed) {
+            Logger.printDebug(() -> "Using previously initialized search");
+            return; // Another thread won the race, and search is already initialized.
+        }
+
+        ByteTrieSearch search = new ByteTrieSearch();
+        String[] split = rawKeywords.split("\n");
+        if (split.length != 0) {
+            // Linked Set so log statement are more organized and easier to read.
+            Set keywords = new LinkedHashSet<>(10 * split.length);
+
+            for (String phrase : split) {
+                // Remove any trailing white space the user may have accidentally included.
+                phrase = phrase.stripTrailing();
+                if (phrase.isBlank()) continue;
+
+                if (phrase.length() < MINIMUM_KEYWORD_LENGTH) {
+                    // Do not reset the setting. Keep the invalid keywords so the user can fix the mistake.
+                    Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_length", phrase, MINIMUM_KEYWORD_LENGTH));
+                    continue;
+                }
+
+                // Add common casing that might appear.
+                //
+                // This could be simplified by adding case insensitive search to the prefix search,
+                // which is very simple to add to StringTreSearch for Unicode and ByteTrieSearch for ASCII.
+                //
+                // But to support Unicode with ByteTrieSearch would require major changes because
+                // UTF-8 characters can be different byte lengths, which does
+                // not allow comparing two different byte arrays using simple plain array indexes.
+                //
+                // Instead add all common case variations of the words.
+                String[] phraseVariations = {
+                        phrase,
+                        phrase.toLowerCase(),
+                        titleCaseFirstWordOnly(phrase),
+                        capitalizeAllFirstLetters(phrase),
+                        phrase.toUpperCase()
+                };
+                if (phrasesWillHideAllVideos(phraseVariations)) {
+                    Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_common", phrase));
+                    continue;
+                }
+
+                keywords.addAll(Arrays.asList(phraseVariations));
+            }
+
+            search.addPatterns(convertStringsToBytes(keywords.toArray(new String[0])));
+            Logger.printDebug(() -> "Search using: (" + search.getEstimatedMemorySize() + " KB) keywords: " + keywords);
+        }
+
+        bufferSearch = search;
+        lastKeywordPhrasesParsed = rawKeywords; // Must set last.
+    }
+
+    public KeywordContentFilter() {
+        // Keywords are parsed on first call to isFiltered()
+        addPathCallbacks(startsWithFilter, containsFilter);
+    }
+
+    private static void logNavigationState(String state) {
+        // Enable locally to debug filtering. Default off to reduce log spam.
+        final boolean LOG_NAVIGATION_STATE = false;
+        // noinspection ConstantValue
+        if (LOG_NAVIGATION_STATE) {
+            Logger.printDebug(() -> "Navigation state: " + state);
+        }
+    }
+
+    @Override
+    public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
+                              StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
+        if (contentIndex != 0 && matchedGroup == startsWithFilter) {
+            return false;
+        }
+
+        if (NavigationBar.isSearchBarActive()) {
+            // Search bar can be active with almost any tab active.
+            if (!Settings.HIDE_KEYWORD_CONTENT_SEARCH.get()) {
+                return false;
+            }
+            logNavigationState("Search");
+        } else if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
+            // For now, consider the under video results the same as the home feed.
+            if (!Settings.HIDE_KEYWORD_CONTENT_HOME.get()) {
+                return false;
+            }
+            logNavigationState("Player active");
+        } else if (NavigationButton.HOME.isSelected()) {
+            // Could use a Switch statement, but there is only 2 tabs of interest.
+            if (!Settings.HIDE_KEYWORD_CONTENT_HOME.get()) {
+                return false;
+            }
+            logNavigationState("Home tab");
+        } else if (NavigationButton.SUBSCRIPTIONS.isSelected()) {
+            if (!Settings.HIDE_SUBSCRIPTIONS_BUTTON.get()) {
+                return false;
+            }
+            logNavigationState("Subscription tab");
+        } else {
+            // User is in the Library or Notifications tab.
+            logNavigationState("Ignored tab");
+            return false;
+        }
+
+        // Field is intentionally compared using reference equality.
+        if (Settings.HIDE_KEYWORD_CONTENT_PHRASES.get() != lastKeywordPhrasesParsed) {
+            // User changed the keywords.
+            parseKeywords();
+        }
+
+        if (!bufferSearch.matches(protobufBufferArray)) {
+            return false;
+        }
+
+        return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/LithoFilterPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/LithoFilterPatch.java
index 51c36493..21b0129a 100644
--- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/LithoFilterPatch.java
+++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/LithoFilterPatch.java
@@ -188,9 +188,8 @@ class ByteArrayFilterGroup extends FilterGroup {
     /**
      * Converts the Strings into byte arrays. Used to search for text in binary data.
      */
-    @RequiresApi(api = Build.VERSION_CODES.N)
     public ByteArrayFilterGroup(BooleanSetting setting, String... filters) {
-        super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
+        super(setting, ByteTrieSearch.convertStringsToBytes(filters));
     }
 
     private synchronized void buildFailurePatterns() {
diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/LicenseActivityHook.java b/app/src/main/java/app/revanced/integrations/youtube/settings/LicenseActivityHook.java
index 30d97a67..3f51c803 100644
--- a/app/src/main/java/app/revanced/integrations/youtube/settings/LicenseActivityHook.java
+++ b/app/src/main/java/app/revanced/integrations/youtube/settings/LicenseActivityHook.java
@@ -70,14 +70,16 @@ public class LicenseActivityHook {
 
     private static void setToolbarTitle(Activity activity, String toolbarTitleResourceName) {
         ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
-        TextView toolbarTextView = Objects.requireNonNull(getChildView(toolbar, view -> view instanceof TextView));
+        TextView toolbarTextView = Objects.requireNonNull(getChildView(toolbar, false,
+                view -> view instanceof TextView));
         toolbarTextView.setText(getResourceIdentifier(toolbarTitleResourceName, "string"));
     }
 
     @SuppressLint("UseCompatLoadingForDrawables")
     private static void setBackButton(Activity activity) {
         ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
-        ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, view -> view instanceof ImageButton));
+        ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, false,
+                view -> view instanceof ImageButton));
         final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
                         ? "yt_outline_arrow_left_white_24"
                         : "yt_outline_arrow_left_black_24",
diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java
index 19da334d..8f2c43e4 100644
--- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java
+++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java
@@ -98,6 +98,11 @@ public class Settings extends BaseSettings {
     public static final BooleanSetting HIDE_IMAGE_SHELF = new BooleanSetting("revanced_hide_image_shelf", TRUE);
     public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", TRUE);
     public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
+    public static final BooleanSetting HIDE_KEYWORD_CONTENT_SEARCH = new BooleanSetting("revanced_hide_keyword_content_search", FALSE);
+    public static final BooleanSetting HIDE_KEYWORD_CONTENT_HOME = new BooleanSetting("revanced_hide_keyword_content_home", FALSE);
+    public static final BooleanSetting HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_keyword_content_subscriptions", FALSE);
+    public static final StringSetting HIDE_KEYWORD_CONTENT_PHRASES = new StringSetting("revanced_hide_keyword_content_phrases", "",
+            parentsAny(HIDE_KEYWORD_CONTENT_SEARCH, HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS));
     public static final BooleanSetting HIDE_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE, true);
     public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
     public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
@@ -227,6 +232,10 @@ public class Settings extends BaseSettings {
             parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
 
     // Debugging
+    /**
+     * When enabled, share the debug logs with care.
+     * The buffer contains select user data, including the client ip address and information that could identify the YT account.
+     */
     public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
 
     // ReturnYoutubeDislike
diff --git a/app/src/main/java/app/revanced/integrations/youtube/shared/NavigationBar.java b/app/src/main/java/app/revanced/integrations/youtube/shared/NavigationBar.java
new file mode 100644
index 00000000..53155a18
--- /dev/null
+++ b/app/src/main/java/app/revanced/integrations/youtube/shared/NavigationBar.java
@@ -0,0 +1,184 @@
+package app.revanced.integrations.youtube.shared;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import androidx.annotation.Nullable;
+import app.revanced.integrations.shared.Logger;
+import app.revanced.integrations.shared.Utils;
+import app.revanced.integrations.youtube.settings.Settings;
+
+import java.lang.ref.WeakReference;
+
+import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton.CREATE;
+
+@SuppressWarnings("unused")
+public final class NavigationBar {
+    private static volatile boolean searchbarIsActive;
+
+    /**
+     * Injection point.
+     */
+    public static void searchBarResultsViewLoaded(View searchbarResults) {
+        searchbarResults.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+            final boolean isActive = searchbarResults.getParent() != null;
+
+            if (searchbarIsActive != isActive) {
+                searchbarIsActive = isActive;
+                Logger.printDebug(() -> "searchbarIsActive: " + isActive);
+            }
+        });
+    }
+
+    public static boolean isSearchBarActive() {
+        return searchbarIsActive;
+    }
+
+
+    /**
+     * Last YT navigation enum loaded.  Not necessarily the active navigation tab.
+     */
+    @Nullable
+    private static volatile String lastYTNavigationEnumName;
+
+    /**
+     * Injection point.
+     */
+    public static void setLastAppNavigationEnum(@Nullable Enum ytNavigationEnumName) {
+        if (ytNavigationEnumName != null) {
+            lastYTNavigationEnumName = ytNavigationEnumName.name();
+        }
+    }
+
+    /**
+     * Injection point.
+     */
+    public static void navigationTabLoaded(final View navigationButtonGroup) {
+        try {
+            String lastEnumName = lastYTNavigationEnumName;
+            for (NavigationButton button : NavigationButton.values()) {
+                if (button.ytEnumName.equals(lastEnumName)) {
+                    ImageView imageView = Utils.getChildView((ViewGroup) navigationButtonGroup,
+                            true, view -> view instanceof ImageView);
+
+                    if (imageView != null) {
+                        Logger.printDebug(() -> "navigationTabLoaded: " + lastEnumName);
+
+                        button.imageViewRef = new WeakReference<>(imageView);
+                        navigationTabCreatedCallback(button, navigationButtonGroup);
+
+                        return;
+                    }
+                }
+            }
+            // Log the unknown tab as exception level, only if debug is enabled.
+            // This is because unknown tabs do no harm, and it's only relevant to developers.
+            if (Settings.DEBUG.get()) {
+                Logger.printException(() -> "Unknown tab: " + lastEnumName
+                        + " view: " + navigationButtonGroup.getClass());
+            }
+        } catch (Exception ex) {
+            Logger.printException(() -> "navigationTabLoaded failure", ex);
+        }
+    }
+
+    /**
+     * Injection point.
+     *
+     * Unique hook just for the 'Create' and 'You' tab.
+     */
+    public static void navigationImageResourceTabLoaded(View view) {
+        // 'You' tab has no YT enum name and the enum hook is not called for it.
+        // Compare the last enum to figure out which tab this actually is.
+        if (CREATE.ytEnumName.equals(lastYTNavigationEnumName)) {
+            navigationTabLoaded(view);
+        } else {
+            lastYTNavigationEnumName = NavigationButton.LIBRARY_YOU.ytEnumName;
+            navigationTabLoaded(view);
+        }
+    }
+
+    /** @noinspection EmptyMethod*/
+    private static void navigationTabCreatedCallback(NavigationBar.NavigationButton button, View tabView) {
+        // Code is added during patching.
+    }
+
+    public enum NavigationButton {
+        HOME("PIVOT_HOME"),
+        SHORTS("TAB_SHORTS"),
+        /**
+         * Create new video tab.
+         *
+         * {@link #isSelected()} always returns false, even if the create video UI is on screen.
+         */
+        CREATE("CREATION_TAB_LARGE"),
+        SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS"),
+        /**
+         * Notifications tab.  Only present when
+         * {@link Settings#SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON} is active.
+         */
+        ACTIVITY("TAB_ACTIVITY"),
+        /**
+         * Library tab when the user is not logged in.
+         */
+        LIBRARY_LOGGED_OUT("ACCOUNT_CIRCLE"),
+        /**
+         * User is logged in with incognito mode enabled.
+         */
+        LIBRARY_INCOGNITO("INCOGNITO_CIRCLE"),
+        /**
+         * Old library tab (pre 'You' layout), only present when version spoofing.
+         */
+        LIBRARY_OLD_UI("VIDEO_LIBRARY_WHITE"),
+        /**
+         * 'You' library tab that is sometimes momentarily loaded.
+         * When this is loaded, {@link #LIBRARY_YOU} is also present.
+         *
+         * This might be a temporary tab while the user profile photo is loading,
+         * but its exact purpose is not entirely clear.
+         */
+        LIBRARY_PIVOT_UNKNOWN("PIVOT_LIBRARY"),
+        /**
+         * Modern library tab with 'You' layout.
+         */
+        // The hooked YT code does not use an enum, and a dummy name is used here.
+        LIBRARY_YOU("YOU_LIBRARY_DUMMY_PLACEHOLDER_NAME");
+
+        /**
+         * @return The active navigation tab.
+         *         If the user is in the create new video UI, this returns NULL.
+         */
+        @Nullable
+        public static NavigationButton getSelectedNavigationButton() {
+            for (NavigationButton button : values()) {
+                if (button.isSelected()) return button;
+            }
+            return null;
+        }
+
+        /**
+         * @return If the currently selected tab is a 'You' or library type.
+         *         Covers all known app states including incognito mode and version spoofing.
+         */
+        public static boolean libraryOrYouTabIsSelected() {
+            return LIBRARY_YOU.isSelected() || LIBRARY_PIVOT_UNKNOWN.isSelected()
+                    || LIBRARY_OLD_UI.isSelected() || LIBRARY_INCOGNITO.isSelected()
+                    || LIBRARY_LOGGED_OUT.isSelected();
+        }
+
+        /**
+         * YouTube enum name for this tab.
+         */
+        private final String ytEnumName;
+        private volatile WeakReference imageViewRef = new WeakReference<>(null);
+
+        NavigationButton(String ytEnumName) {
+            this.ytEnumName = ytEnumName;
+        }
+
+        public boolean isSelected() {
+            ImageView view = imageViewRef.get();
+            return view != null && view.isSelected();
+        }
+    }
+}
diff --git a/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerType.kt b/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerType.kt
index 15d1496d..7db4a3fd 100644
--- a/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerType.kt
+++ b/app/src/main/java/app/revanced/integrations/youtube/shared/PlayerType.kt
@@ -132,4 +132,8 @@ enum class PlayerType {
     fun isNoneHiddenOrMinimized(): Boolean {
         return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED
     }
+
+    fun isMaximizedOrFullscreen(): Boolean {
+        return this == WATCH_WHILE_MAXIMIZED || this == WATCH_WHILE_FULLSCREEN
+    }
 }

From 910b03e9a35cacb016f72d7304b2e3cedcd56594 Mon Sep 17 00:00:00 2001
From: semantic-release-bot 
Date: Wed, 27 Mar 2024 13:12:43 +0000
Subject: [PATCH 37/40] chore(release): 1.5.0-dev.9 [skip ci]

# [1.5.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.8...v1.5.0-dev.9) (2024-03-27)

### Bug Fixes

* Check index of pattern in string instead of the other way around ([96a1e46](https://github.com/ReVanced/revanced-integrations/commit/96a1e4680d23be7154bb83290b1887fcd1a22f53))
* **YouTube - Hide layout components:** Correctly hide Join button ([b945e2f](https://github.com/ReVanced/revanced-integrations/commit/b945e2f44b1a62326e6d45345c1467668d803f53))
* **YouTube - Hide Shorts components:** Correctly hide join button ([b7a8995](https://github.com/ReVanced/revanced-integrations/commit/b7a8995f798e386ee1d9ab5bbd857c1736cc5a29))
* **YouTube:** Fix video playback by switching to ReVanced GmsCore vendor ([#589](https://github.com/ReVanced/revanced-integrations/issues/589)) ([6e947e2](https://github.com/ReVanced/revanced-integrations/commit/6e947e24c2ac36e1bddcd25412870a36aa6c85c8))

### Features

* **YouTube - Hide layout components:** Filter home/search results by keywords ([#584](https://github.com/ReVanced/revanced-integrations/issues/584)) ([0cbad98](https://github.com/ReVanced/revanced-integrations/commit/0cbad9820577c476f1f29b6ac77611b38afbb950))
* **YouTube - Hide Shorts components:** Hide like and dislike buttons ([2a08e5a](https://github.com/ReVanced/revanced-integrations/commit/2a08e5a98e9e9a00bb306313ff487d67c042a92e))
* **YouTube - Hide Shorts components:** Hide sound metadata label ([46d8ef6](https://github.com/ReVanced/revanced-integrations/commit/46d8ef6f88bd4c912a45357541291af38b5fc81f))
* **YouTube - Hide Shorts components:** Hide title and full video link label ([59165de](https://github.com/ReVanced/revanced-integrations/commit/59165de801a5481fa4055dcf1797fe84dce235c0))
---
 CHANGELOG.md      | 18 ++++++++++++++++++
 gradle.properties |  2 +-
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7abded65..1b447210 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,21 @@
+# [1.5.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.8...v1.5.0-dev.9) (2024-03-27)
+
+
+### Bug Fixes
+
+* Check index of pattern in string instead of the other way around ([96a1e46](https://github.com/ReVanced/revanced-integrations/commit/96a1e4680d23be7154bb83290b1887fcd1a22f53))
+* **YouTube - Hide layout components:** Correctly hide Join button ([b945e2f](https://github.com/ReVanced/revanced-integrations/commit/b945e2f44b1a62326e6d45345c1467668d803f53))
+* **YouTube - Hide Shorts components:** Correctly hide join button ([b7a8995](https://github.com/ReVanced/revanced-integrations/commit/b7a8995f798e386ee1d9ab5bbd857c1736cc5a29))
+* **YouTube:** Fix video playback by switching to ReVanced GmsCore vendor ([#589](https://github.com/ReVanced/revanced-integrations/issues/589)) ([6e947e2](https://github.com/ReVanced/revanced-integrations/commit/6e947e24c2ac36e1bddcd25412870a36aa6c85c8))
+
+
+### Features
+
+* **YouTube - Hide layout components:** Filter home/search results by keywords ([#584](https://github.com/ReVanced/revanced-integrations/issues/584)) ([0cbad98](https://github.com/ReVanced/revanced-integrations/commit/0cbad9820577c476f1f29b6ac77611b38afbb950))
+* **YouTube - Hide Shorts components:** Hide like and dislike buttons ([2a08e5a](https://github.com/ReVanced/revanced-integrations/commit/2a08e5a98e9e9a00bb306313ff487d67c042a92e))
+* **YouTube - Hide Shorts components:** Hide sound metadata label ([46d8ef6](https://github.com/ReVanced/revanced-integrations/commit/46d8ef6f88bd4c912a45357541291af38b5fc81f))
+* **YouTube - Hide Shorts components:** Hide title and full video link label ([59165de](https://github.com/ReVanced/revanced-integrations/commit/59165de801a5481fa4055dcf1797fe84dce235c0))
+
 # [1.5.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.7...v1.5.0-dev.8) (2024-03-25)
 
 
diff --git a/gradle.properties b/gradle.properties
index 582b936e..66bc2777 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
 org.gradle.parallel = true
 org.gradle.caching = true
 android.useAndroidX = true
-version = 1.5.0-dev.8
+version = 1.5.0-dev.9

From 4ae7d5b1780ee146b1e6810b7ba007e5a6bcacf4 Mon Sep 17 00:00:00 2001
From: oSumAtrIX 
Date: Wed, 27 Mar 2024 14:16:34 +0100
Subject: [PATCH 38/40] refactor: Move integrations to correct package

---
 .../{youtube/patches => shared}/GmsCoreSupport.java           | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
 rename app/src/main/java/app/revanced/integrations/{youtube/patches => shared}/GmsCoreSupport.java (94%)

diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/GmsCoreSupport.java b/app/src/main/java/app/revanced/integrations/shared/GmsCoreSupport.java
similarity index 94%
rename from app/src/main/java/app/revanced/integrations/youtube/patches/GmsCoreSupport.java
rename to app/src/main/java/app/revanced/integrations/shared/GmsCoreSupport.java
index ddf4786e..3dcb44a5 100644
--- a/app/src/main/java/app/revanced/integrations/youtube/patches/GmsCoreSupport.java
+++ b/app/src/main/java/app/revanced/integrations/shared/GmsCoreSupport.java
@@ -1,4 +1,4 @@
-package app.revanced.integrations.youtube.patches;
+package app.revanced.integrations.shared;
 
 import android.app.SearchManager;
 import android.content.Context;
@@ -7,8 +7,6 @@ import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Build;
 import androidx.annotation.RequiresApi;
-import app.revanced.integrations.shared.Logger;
-import app.revanced.integrations.shared.Utils;
 
 import java.util.Objects;
 

From 1ee99aa6f0b4af15eeca25c7e21e8a0f5e9d189a Mon Sep 17 00:00:00 2001
From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Date: Wed, 27 Mar 2024 17:44:01 +0400
Subject: [PATCH 39/40] feat(YouTube - Hide Shorts components): Selectively
 hide Shorts for home / subscription / search (#592)

---
 .../components/KeywordContentFilter.java      | 70 +++++++++----------
 .../patches/components/ShortsFilter.java      | 58 ++++++++++-----
 .../youtube/settings/Settings.java            | 17 ++++-
 3 files changed, 89 insertions(+), 56 deletions(-)

diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java
index 6ea91beb..0cbc8f71 100644
--- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java
+++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java
@@ -112,6 +112,37 @@ final class KeywordContentFilter extends Filter {
 
     private volatile ByteTrieSearch bufferSearch;
 
+    private static void logNavigationState(String state) {
+        // Enable locally to debug filtering. Default off to reduce log spam.
+        final boolean LOG_NAVIGATION_STATE = false;
+        // noinspection ConstantValue
+        if (LOG_NAVIGATION_STATE) {
+            Logger.printDebug(() -> "Navigation state: " + state);
+        }
+    }
+
+    private static boolean hideKeywordSettingIsActive() {
+        if (NavigationBar.isSearchBarActive()) {
+            // Must check first. Search bar can be active with almost any tab.
+            logNavigationState("Search");
+            return Settings.HIDE_KEYWORD_CONTENT_SEARCH.get();
+        } else if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
+            // For now, consider the under video results the same as the home feed.
+            logNavigationState("Player active");
+            return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
+        } else if (NavigationButton.HOME.isSelected()) {
+            logNavigationState("Home tab");
+            return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
+        } else if (NavigationButton.SUBSCRIPTIONS.isSelected()) {
+            logNavigationState("Subscription tab");
+            return Settings.HIDE_SUBSCRIPTIONS_BUTTON.get();
+        } else {
+            // User is in the Library or Notifications tab.
+            logNavigationState("Ignored tab");
+        }
+        return false;
+    }
+
     /**
      * Change first letter of the first word to use title case.
      */
@@ -224,15 +255,6 @@ final class KeywordContentFilter extends Filter {
         addPathCallbacks(startsWithFilter, containsFilter);
     }
 
-    private static void logNavigationState(String state) {
-        // Enable locally to debug filtering. Default off to reduce log spam.
-        final boolean LOG_NAVIGATION_STATE = false;
-        // noinspection ConstantValue
-        if (LOG_NAVIGATION_STATE) {
-            Logger.printDebug(() -> "Navigation state: " + state);
-        }
-    }
-
     @Override
     public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
                               StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
@@ -240,34 +262,7 @@ final class KeywordContentFilter extends Filter {
             return false;
         }
 
-        if (NavigationBar.isSearchBarActive()) {
-            // Search bar can be active with almost any tab active.
-            if (!Settings.HIDE_KEYWORD_CONTENT_SEARCH.get()) {
-                return false;
-            }
-            logNavigationState("Search");
-        } else if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
-            // For now, consider the under video results the same as the home feed.
-            if (!Settings.HIDE_KEYWORD_CONTENT_HOME.get()) {
-                return false;
-            }
-            logNavigationState("Player active");
-        } else if (NavigationButton.HOME.isSelected()) {
-            // Could use a Switch statement, but there is only 2 tabs of interest.
-            if (!Settings.HIDE_KEYWORD_CONTENT_HOME.get()) {
-                return false;
-            }
-            logNavigationState("Home tab");
-        } else if (NavigationButton.SUBSCRIPTIONS.isSelected()) {
-            if (!Settings.HIDE_SUBSCRIPTIONS_BUTTON.get()) {
-                return false;
-            }
-            logNavigationState("Subscription tab");
-        } else {
-            // User is in the Library or Notifications tab.
-            logNavigationState("Ignored tab");
-            return false;
-        }
+        if (!hideKeywordSettingIsActive()) return false;
 
         // Field is intentionally compared using reference equality.
         if (Settings.HIDE_KEYWORD_CONTENT_PHRASES.get() != lastKeywordPhrasesParsed) {
@@ -281,4 +276,5 @@ final class KeywordContentFilter extends Filter {
 
         return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
     }
+
 }
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java
index 8bbcae7f..90a64f56 100644
--- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java
+++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java
@@ -1,15 +1,19 @@
 package app.revanced.integrations.youtube.patches.components;
 
+import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
+
 import android.os.Build;
 import android.view.View;
+
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
-import app.revanced.integrations.youtube.settings.Settings;
 import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
 
-import static app.revanced.integrations.shared.Utils.hideViewBy1dpUnderCondition;
-import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
+import app.revanced.integrations.shared.Utils;
+import app.revanced.integrations.youtube.settings.Settings;
+import app.revanced.integrations.youtube.shared.NavigationBar;
+import app.revanced.integrations.youtube.shared.PlayerType;
 
 @SuppressWarnings("unused")
 @RequiresApi(api = Build.VERSION_CODES.N)
@@ -35,8 +39,10 @@ public final class ShortsFilter extends Filter {
     private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
 
     public ShortsFilter() {
+        // Identifier components.
+
         var shorts = new StringFilterGroup(
-                Settings.HIDE_SHORTS,
+                null, // Setting is based on navigation state.
                 "shorts_shelf",
                 "inline_shorts",
                 "shorts_grid",
@@ -46,7 +52,7 @@ public final class ShortsFilter extends Filter {
         // Feed Shorts shelf header.
         // Use a different filter group for this pattern, as it requires an additional check after matching.
         shelfHeader = new StringFilterGroup(
-                Settings.HIDE_SHORTS,
+                null,
                 "shelf_header.eml"
         );
 
@@ -58,14 +64,14 @@ public final class ShortsFilter extends Filter {
 
         addIdentifierCallbacks(shorts, shelfHeader, thanksButton);
 
+        // Path components.
+
         // Shorts that appear in the feed/search when the device is using tablet layout.
-        shortsCompactFeedVideoPath = new StringFilterGroup(Settings.HIDE_SHORTS,
-                "compact_video.eml");
+        shortsCompactFeedVideoPath = new StringFilterGroup(null, "compact_video.eml");
         // Filter out items that use the 'frame0' thumbnail.
         // This is a valid thumbnail for both regular videos and Shorts,
         // but it appears these thumbnails are used only for Shorts.
-        shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(Settings.HIDE_SHORTS,
-                "/frame0.jpg");
+        shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(null, "/frame0.jpg");
 
         // Shorts player components.
         joinButton = new StringFilterGroup(
@@ -174,7 +180,8 @@ public final class ShortsFilter extends Filter {
             ) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
 
             if (matchedGroup == shortsCompactFeedVideoPath) {
-                if (contentIndex == 0 && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
+                if (shouldHideShortsFeedItems() && contentIndex == 0
+                        && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
                     return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
                 }
                 return false;
@@ -195,22 +202,41 @@ public final class ShortsFilter extends Filter {
             ) {
                 if (path.startsWith(REEL_CHANNEL_BAR_PATH)) return super.isFiltered(
                         identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
-                );
+                ); // else, return false.
             }
 
             return false;
-        } else if (matchedGroup == shelfHeader) {
-            // Because the header is used in watch history and possibly other places, check for the index,
-            // which is 0 when the shelf header is used for Shorts.
-            if (contentIndex != 0) return false;
+        } else {
+            // Feed/search path components.
+            if (matchedGroup == shelfHeader) {
+                // Because the header is used in watch history and possibly other places, check for the index,
+                // which is 0 when the shelf header is used for Shorts.
+                if (contentIndex != 0) return false;
+            }
+
+            if (!shouldHideShortsFeedItems()) return false;
         }
 
         // Super class handles logging.
         return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
     }
 
+    private static boolean shouldHideShortsFeedItems() {
+        if (NavigationBar.isSearchBarActive()) { // Must check search first.
+            return Settings.HIDE_SHORTS_SEARCH.get();
+        } else if (PlayerType.getCurrent().isMaximizedOrFullscreen()
+                || NavigationBar.NavigationButton.HOME.isSelected()) {
+            return Settings.HIDE_SHORTS_HOME.get();
+        } else if (NavigationBar.NavigationButton.SUBSCRIPTIONS.isSelected()) {
+            return Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
+        }
+        return false;
+    }
+
     public static void hideShortsShelf(final View shortsShelfView) {
-        hideViewBy1dpUnderCondition(Settings.HIDE_SHORTS, shortsShelfView);
+        if (shouldHideShortsFeedItems()) {
+            Utils.hideViewByLayoutParams(shortsShelfView);
+        }
     }
 
     // region Hide the buttons in older versions of YouTube. New versions use Litho.
diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java
index 8f2c43e4..1bb6ecaa 100644
--- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java
+++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java
@@ -98,11 +98,11 @@ public class Settings extends BaseSettings {
     public static final BooleanSetting HIDE_IMAGE_SHELF = new BooleanSetting("revanced_hide_image_shelf", TRUE);
     public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", TRUE);
     public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
-    public static final BooleanSetting HIDE_KEYWORD_CONTENT_SEARCH = new BooleanSetting("revanced_hide_keyword_content_search", FALSE);
     public static final BooleanSetting HIDE_KEYWORD_CONTENT_HOME = new BooleanSetting("revanced_hide_keyword_content_home", FALSE);
     public static final BooleanSetting HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_keyword_content_subscriptions", FALSE);
+    public static final BooleanSetting HIDE_KEYWORD_CONTENT_SEARCH = new BooleanSetting("revanced_hide_keyword_content_search", FALSE);
     public static final StringSetting HIDE_KEYWORD_CONTENT_PHRASES = new StringSetting("revanced_hide_keyword_content_phrases", "",
-            parentsAny(HIDE_KEYWORD_CONTENT_SEARCH, HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS));
+            parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
     public static final BooleanSetting HIDE_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE, true);
     public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
     public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
@@ -141,7 +141,10 @@ public class Settings extends BaseSettings {
     public static final BooleanSetting HIDE_TRANSCIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
 
     // Shorts
-    public static final BooleanSetting HIDE_SHORTS = new BooleanSetting("revanced_hide_shorts", FALSE, true);
+    @Deprecated public static final BooleanSetting DEPRECATED_HIDE_SHORTS = new BooleanSetting("revanced_hide_shorts", FALSE);
+    public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);
+    public static final BooleanSetting HIDE_SHORTS_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_shorts_subscriptions", FALSE);
+    public static final BooleanSetting HIDE_SHORTS_SEARCH = new BooleanSetting("revanced_hide_shorts_search", FALSE);
     public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE);
     public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_shorts_subscribe_button", TRUE);
     public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED = new BooleanSetting("revanced_hide_shorts_subscribe_button_paused", FALSE);
@@ -362,6 +365,14 @@ public class Settings extends BaseSettings {
         // Remove any previously saved announcement consumer (a random generated string).
         Setting.preferences.saveString("revanced_announcement_consumer", null);
 
+        // Shorts
+        if (DEPRECATED_HIDE_SHORTS.get()) {
+            Logger.printInfo(() -> "Migrating hide Shorts setting");
+            DEPRECATED_HIDE_SHORTS.resetToDefault();
+            HIDE_SHORTS_HOME.save(true);
+            HIDE_SHORTS_SUBSCRIPTIONS.save(true);
+            HIDE_SHORTS_SEARCH.save(true);
+        }
 
         // endregion
     }

From 633e3654669d2ffada21e30b9b2cb8f4d69a31d2 Mon Sep 17 00:00:00 2001
From: semantic-release-bot 
Date: Wed, 27 Mar 2024 13:47:23 +0000
Subject: [PATCH 40/40] chore(release): 1.5.0-dev.10 [skip ci]

# [1.5.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.9...v1.5.0-dev.10) (2024-03-27)

### Features

* **YouTube - Hide Shorts components:** Selectively hide Shorts for home / subscription / search ([#592](https://github.com/ReVanced/revanced-integrations/issues/592)) ([1ee99aa](https://github.com/ReVanced/revanced-integrations/commit/1ee99aa6f0b4af15eeca25c7e21e8a0f5e9d189a))
---
 CHANGELOG.md      | 7 +++++++
 gradle.properties | 2 +-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1b447210..78dd5588 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+# [1.5.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.9...v1.5.0-dev.10) (2024-03-27)
+
+
+### Features
+
+* **YouTube - Hide Shorts components:** Selectively hide Shorts for home / subscription / search ([#592](https://github.com/ReVanced/revanced-integrations/issues/592)) ([1ee99aa](https://github.com/ReVanced/revanced-integrations/commit/1ee99aa6f0b4af15eeca25c7e21e8a0f5e9d189a))
+
 # [1.5.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.8...v1.5.0-dev.9) (2024-03-27)
 
 
diff --git a/gradle.properties b/gradle.properties
index 66bc2777..bcaa9a12 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
 org.gradle.parallel = true
 org.gradle.caching = true
 android.useAndroidX = true
-version = 1.5.0-dev.9
+version = 1.5.0-dev.10