mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-02-18 23:16:50 +01:00
fix(YouTube - Hide Shorts components): Correctly hide Shorts if navigation tab is changed using device back button (#611)
This commit is contained in:
parent
4939a22da1
commit
ffc3437843
@ -1,19 +1,15 @@
|
|||||||
package app.revanced.integrations.youtube.patches;
|
package app.revanced.integrations.youtube.patches;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
import static app.revanced.integrations.youtube.settings.Settings.*;
|
||||||
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
|
||||||
import app.revanced.integrations.shared.settings.EnumSetting;
|
|
||||||
import app.revanced.integrations.shared.settings.Setting;
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
import app.revanced.integrations.youtube.shared.NavigationBar;
|
|
||||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
|
||||||
|
|
||||||
import org.chromium.net.UrlRequest;
|
import org.chromium.net.UrlRequest;
|
||||||
import org.chromium.net.UrlResponseInfo;
|
import org.chromium.net.UrlResponseInfo;
|
||||||
import org.chromium.net.impl.CronetUrlRequest;
|
import org.chromium.net.impl.CronetUrlRequest;
|
||||||
@ -26,13 +22,12 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import static app.revanced.integrations.shared.StringRef.str;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_HOME;
|
import app.revanced.integrations.shared.Utils;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_LIBRARY;
|
import app.revanced.integrations.shared.settings.Setting;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_PLAYER;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_SEARCH;
|
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_SUBSCRIPTIONS;
|
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternative YouTube thumbnails.
|
* Alternative YouTube thumbnails.
|
||||||
@ -134,11 +129,6 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
*/
|
*/
|
||||||
private static volatile long timeToResumeDeArrowAPICalls;
|
private static volatile long timeToResumeDeArrowAPICalls;
|
||||||
|
|
||||||
/**
|
|
||||||
* Used only for debug logging.
|
|
||||||
*/
|
|
||||||
private static volatile EnumSetting<ThumbnailOption> currentOptionSetting;
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
dearrowApiUri = validateSettings();
|
dearrowApiUri = validateSettings();
|
||||||
final int port = dearrowApiUri.getPort();
|
final int port = dearrowApiUri.getPort();
|
||||||
@ -162,23 +152,38 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
return apiUri;
|
return apiUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EnumSetting<ThumbnailOption> optionSettingForCurrentNavigation() {
|
private static ThumbnailOption optionSettingForCurrentNavigation() {
|
||||||
// Must check player type first, as search bar can be active behind the player.
|
// Must check player type first, as search bar can be active behind the player.
|
||||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||||
return ALT_THUMBNAIL_PLAYER;
|
return ALT_THUMBNAIL_PLAYER.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must check second, as search can be from any tab.
|
// Must check second, as search can be from any tab.
|
||||||
if (NavigationBar.isSearchBarActive()) {
|
if (NavigationBar.isSearchBarActive()) {
|
||||||
return ALT_THUMBNAIL_SEARCH;
|
return ALT_THUMBNAIL_SEARCH.get();
|
||||||
}
|
}
|
||||||
if (NavigationButton.HOME.isSelected()) {
|
|
||||||
return ALT_THUMBNAIL_HOME;
|
// Avoid checking which navigation button is selected, if all other settings are the same.
|
||||||
|
ThumbnailOption homeOption = ALT_THUMBNAIL_HOME.get();
|
||||||
|
ThumbnailOption subscriptionsOption = ALT_THUMBNAIL_SUBSCRIPTIONS.get();
|
||||||
|
ThumbnailOption libraryOption = ALT_THUMBNAIL_LIBRARY.get();
|
||||||
|
if ((homeOption == subscriptionsOption) && (homeOption == libraryOption)) {
|
||||||
|
return homeOption; // All are the same option.
|
||||||
}
|
}
|
||||||
if (NavigationButton.SUBSCRIPTIONS.isSelected() || NavigationButton.NOTIFICATIONS.isSelected()) {
|
|
||||||
return ALT_THUMBNAIL_SUBSCRIPTIONS;
|
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||||
|
if (selectedNavButton == null) {
|
||||||
|
// Unknown tab, treat as the home tab;
|
||||||
|
return homeOption;
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.HOME) {
|
||||||
|
return homeOption;
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS || selectedNavButton == NavigationButton.NOTIFICATIONS) {
|
||||||
|
return subscriptionsOption;
|
||||||
}
|
}
|
||||||
// A library tab variant is active.
|
// A library tab variant is active.
|
||||||
return ALT_THUMBNAIL_LIBRARY;
|
return libraryOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -256,14 +261,7 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
*/
|
*/
|
||||||
public static String overrideImageURL(String originalUrl) {
|
public static String overrideImageURL(String originalUrl) {
|
||||||
try {
|
try {
|
||||||
EnumSetting<ThumbnailOption> optionSetting = optionSettingForCurrentNavigation();
|
ThumbnailOption option = optionSettingForCurrentNavigation();
|
||||||
ThumbnailOption option = optionSetting.get();
|
|
||||||
if (BaseSettings.DEBUG.get()) {
|
|
||||||
if (currentOptionSetting != optionSetting) {
|
|
||||||
currentOptionSetting = optionSetting;
|
|
||||||
Logger.printDebug(() -> "Changed to setting: " + optionSetting.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option == ThumbnailOption.ORIGINAL) {
|
if (option == ThumbnailOption.ORIGINAL) {
|
||||||
return originalUrl;
|
return originalUrl;
|
||||||
|
@ -112,37 +112,35 @@ final class KeywordContentFilter extends Filter {
|
|||||||
|
|
||||||
private volatile ByteTrieSearch bufferSearch;
|
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() {
|
private static boolean hideKeywordSettingIsActive() {
|
||||||
// Must check player type first, as search bar can be active behind the player.
|
// Must check player type first, as search bar can be active behind the player.
|
||||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||||
// For now, consider the under video results the same as the home feed.
|
// For now, consider the under video results the same as the home feed.
|
||||||
logNavigationState("Player active");
|
|
||||||
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||||
}
|
}
|
||||||
// Must check second, as search can be from any tab.
|
// Must check second, as search can be from any tab.
|
||||||
if (NavigationBar.isSearchBarActive()) {
|
if (NavigationBar.isSearchBarActive()) {
|
||||||
logNavigationState("Search");
|
|
||||||
return Settings.HIDE_KEYWORD_CONTENT_SEARCH.get();
|
return Settings.HIDE_KEYWORD_CONTENT_SEARCH.get();
|
||||||
}
|
}
|
||||||
if (NavigationButton.HOME.isSelected()) {
|
|
||||||
logNavigationState("Home tab");
|
// Avoid checking navigation button status if all other settings are off.
|
||||||
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
final boolean hideHome = Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||||
|
final boolean hideSubscriptions = Settings.HIDE_SUBSCRIPTIONS_BUTTON.get();
|
||||||
|
if (!hideHome && !hideSubscriptions) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (NavigationButton.SUBSCRIPTIONS.isSelected()) {
|
|
||||||
logNavigationState("Subscription tab");
|
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||||
return Settings.HIDE_SUBSCRIPTIONS_BUTTON.get();
|
if (selectedNavButton == null) {
|
||||||
|
return hideHome; // Unknown tab, treat the same as home.
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.HOME) {
|
||||||
|
return hideHome;
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
|
||||||
|
return hideSubscriptions;
|
||||||
}
|
}
|
||||||
// User is in the Library or Notifications tab.
|
// User is in the Library or Notifications tab.
|
||||||
logNavigationState("Ignored tab");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
package app.revanced.integrations.youtube.patches.components;
|
package app.revanced.integrations.youtube.patches.components;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
import app.revanced.integrations.youtube.StringTrieSearch;
|
import app.revanced.integrations.youtube.StringTrieSearch;
|
||||||
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
import app.revanced.integrations.youtube.shared.NavigationBar;
|
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@ -366,13 +368,18 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hideShelves() {
|
private static boolean hideShelves() {
|
||||||
|
// If the player is opened while library is selected,
|
||||||
|
// then filter any recommendations below the player.
|
||||||
|
if (PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||||
|
// Or if the search is active while library is selected, then also filter.
|
||||||
|
|| NavigationBar.isSearchBarActive()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check navigation button last.
|
||||||
// Only filter if the library tab is not selected.
|
// Only filter if the library tab is not selected.
|
||||||
// This check is important as the shelf layout is used for the library tab playlists.
|
// This check is important as the shelf layout is used for the library tab playlists.
|
||||||
return !NavigationBar.NavigationButton.libraryOrYouTabIsSelected()
|
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||||
// But if the player is opened while library is selected,
|
return selectedNavButton != null && !selectedNavButton.isLibraryOrYouTab();
|
||||||
// then still filter any recommendations below the player.
|
|
||||||
|| PlayerType.getCurrent().isMaximizedOrFullscreen()
|
|
||||||
// Or if the search is active while library is selected, then also filter.
|
|
||||||
|| NavigationBar.isSearchBarActive();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package app.revanced.integrations.youtube.patches.components;
|
package app.revanced.integrations.youtube.patches.components;
|
||||||
|
|
||||||
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
||||||
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@ -224,16 +225,30 @@ public final class ShortsFilter extends Filter {
|
|||||||
// For now, consider the under video results the same as the home feed.
|
// For now, consider the under video results the same as the home feed.
|
||||||
return Settings.HIDE_SHORTS_HOME.get();
|
return Settings.HIDE_SHORTS_HOME.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must check second, as search can be from any tab.
|
// Must check second, as search can be from any tab.
|
||||||
if (NavigationBar.isSearchBarActive()) {
|
if (NavigationBar.isSearchBarActive()) {
|
||||||
return Settings.HIDE_SHORTS_SEARCH.get();
|
return Settings.HIDE_SHORTS_SEARCH.get();
|
||||||
}
|
}
|
||||||
if (NavigationBar.NavigationButton.HOME.isSelected()) {
|
|
||||||
return Settings.HIDE_SHORTS_HOME.get();
|
// Avoid checking navigation button status if all other settings are off.
|
||||||
|
final boolean hideHome = Settings.HIDE_SHORTS_HOME.get();
|
||||||
|
final boolean hideSubscriptions = Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
||||||
|
if (!hideHome && !hideSubscriptions) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (NavigationBar.NavigationButton.SUBSCRIPTIONS.isSelected()) {
|
|
||||||
return Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||||
|
if (selectedNavButton == null) {
|
||||||
|
return hideHome; // Unknown tab, treat the same as home.
|
||||||
}
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.HOME) {
|
||||||
|
return hideHome;
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
|
||||||
|
return hideSubscriptions;
|
||||||
|
}
|
||||||
|
// User must be in the library tab. Don't hide the history or any playlists here.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,21 +2,29 @@ package app.revanced.integrations.youtube.shared;
|
|||||||
|
|
||||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.integrations.shared.Utils;
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class NavigationBar {
|
public final class NavigationBar {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Search bar
|
||||||
|
//
|
||||||
|
|
||||||
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
|
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,11 +44,101 @@ public final class NavigationBar {
|
|||||||
return searchbarResults != null && searchbarResults.getParent() != null;
|
return searchbarResults != null && searchbarResults.getParent() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Navigation bar buttons
|
||||||
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last YT navigation enum loaded. Not necessarily the active navigation tab.
|
* How long to wait for the set nav button latch to be released. Maximum wait time must
|
||||||
|
* be as small as possible while still allowing enough time for the nav bar to update.
|
||||||
|
*
|
||||||
|
* YT calls it's back button handlers out of order,
|
||||||
|
* and litho starts filtering before the navigation bar is updated.
|
||||||
|
*
|
||||||
|
* Fixing this situation and not needlessly waiting requires somehow
|
||||||
|
* detecting if a back button key-press will cause a tab change.
|
||||||
|
*
|
||||||
|
* Typically after pressing the back button, the time between the first litho event and
|
||||||
|
* when the nav button is updated is about 10-20ms. Using 50-100ms here should be enough time
|
||||||
|
* and not noticeable, since YT typically takes 100-200ms (or more) to update the view anyways.
|
||||||
|
*
|
||||||
|
* This issue can also be avoided on a patch by patch basis, by avoiding calls to
|
||||||
|
* {@link NavigationButton#getSelectedNavigationButton()} unless absolutely necessary.
|
||||||
|
*/
|
||||||
|
private static final long LATCH_AWAIT_TIMEOUT_MILLISECONDS = 75;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as a workaround to fix the issue of YT calling back button handlers out of order.
|
||||||
|
* Used to hold calls to {@link NavigationButton#getSelectedNavigationButton()}
|
||||||
|
* until the current navigation button can be determined.
|
||||||
|
*
|
||||||
|
* Only used when the hardware back button is pressed.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static volatile String lastYTNavigationEnumName;
|
private static volatile CountDownLatch navButtonLatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of nav button layout views to Enum type.
|
||||||
|
* No synchronization is needed, and this is always accessed from the main thread.
|
||||||
|
*/
|
||||||
|
private static final Map<View, NavigationButton> viewToButtonMap = new WeakHashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// On app startup litho can start before the navigation bar is initialized.
|
||||||
|
// Force it to wait until the nav bar is updated.
|
||||||
|
createNavButtonLatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createNavButtonLatch() {
|
||||||
|
navButtonLatch = new CountDownLatch(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void releaseNavButtonLatch() {
|
||||||
|
CountDownLatch latch = navButtonLatch;
|
||||||
|
if (latch != null) {
|
||||||
|
navButtonLatch = null;
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void waitForNavButtonLatchIfNeeded() {
|
||||||
|
CountDownLatch latch = navButtonLatch;
|
||||||
|
if (latch == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Utils.isCurrentlyOnMainThread()) {
|
||||||
|
// The latch is released from the main thread, and waiting from the main thread will always timeout.
|
||||||
|
// This situation has only been observed when navigating out of a submenu and not changing tabs.
|
||||||
|
// and for that use case the nav bar does not change so it's safe to return here.
|
||||||
|
Logger.printDebug(() -> "Cannot block main thread waiting for nav button. Using last known navbar button status.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Logger.printDebug(() -> "Latch wait started");
|
||||||
|
if (latch.await(LATCH_AWAIT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS)) {
|
||||||
|
// Back button changed the navigation tab.
|
||||||
|
Logger.printDebug(() -> "Latch wait complete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout occurred, and a normal event when pressing the physical back button
|
||||||
|
// does not change navigation tabs.
|
||||||
|
releaseNavButtonLatch(); // Prevent other threads from waiting for no reason.
|
||||||
|
Logger.printDebug(() -> "Latch wait timed out");
|
||||||
|
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Logger.printException(() -> "Latch wait interrupted failure", ex); // Will never happen.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last YT navigation enum loaded. Not necessarily the active navigation tab.
|
||||||
|
* Always accessed from the main thread.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static String lastYTNavigationEnumName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
@ -57,21 +155,16 @@ public final class NavigationBar {
|
|||||||
public static void navigationTabLoaded(final View navigationButtonGroup) {
|
public static void navigationTabLoaded(final View navigationButtonGroup) {
|
||||||
try {
|
try {
|
||||||
String lastEnumName = lastYTNavigationEnumName;
|
String lastEnumName = lastYTNavigationEnumName;
|
||||||
|
|
||||||
for (NavigationButton button : NavigationButton.values()) {
|
for (NavigationButton button : NavigationButton.values()) {
|
||||||
if (button.ytEnumName.equals(lastEnumName)) {
|
if (button.ytEnumName.equals(lastEnumName)) {
|
||||||
ImageView imageView = Utils.getChildView((ViewGroup) navigationButtonGroup,
|
Logger.printDebug(() -> "navigationTabLoaded: " + lastEnumName);
|
||||||
true, view -> view instanceof ImageView);
|
viewToButtonMap.put(navigationButtonGroup, button);
|
||||||
|
navigationTabCreatedCallback(button, navigationButtonGroup);
|
||||||
if (imageView != null) {
|
return;
|
||||||
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.
|
// 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.
|
// This is because unknown tabs do no harm, and it's only relevant to developers.
|
||||||
if (Settings.DEBUG.get()) {
|
if (Settings.DEBUG.get()) {
|
||||||
@ -99,6 +192,46 @@ public final class NavigationBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void navigationTabSelected(View navButtonImageView, boolean isSelected) {
|
||||||
|
try {
|
||||||
|
NavigationButton button = viewToButtonMap.get(navButtonImageView);
|
||||||
|
|
||||||
|
if (button == null) { // An unknown tab was selected.
|
||||||
|
// Show a toast only if debug mode is enabled.
|
||||||
|
if (BaseSettings.DEBUG.get()) {
|
||||||
|
Logger.printException(() -> "Unknown navigation view selected: " + navButtonImageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationButton.selectedNavigationButton = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
NavigationButton.selectedNavigationButton = button;
|
||||||
|
Logger.printDebug(() -> "Changed to navigation button: " + button);
|
||||||
|
|
||||||
|
// Release any threads waiting for the selected nav button.
|
||||||
|
releaseNavButtonLatch();
|
||||||
|
} else if (NavigationButton.selectedNavigationButton == button) {
|
||||||
|
NavigationButton.selectedNavigationButton = null;
|
||||||
|
Logger.printDebug(() -> "Navigated away from button: " + button);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "navigationTabSelected failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void onBackPressed(Activity activity) {
|
||||||
|
Logger.printDebug(() -> "Back button pressed");
|
||||||
|
createNavButtonLatch();
|
||||||
|
}
|
||||||
|
|
||||||
/** @noinspection EmptyMethod*/
|
/** @noinspection EmptyMethod*/
|
||||||
private static void navigationTabCreatedCallback(NavigationButton button, View tabView) {
|
private static void navigationTabCreatedCallback(NavigationButton button, View tabView) {
|
||||||
// Code is added during patching.
|
// Code is added during patching.
|
||||||
@ -109,8 +242,7 @@ public final class NavigationBar {
|
|||||||
SHORTS("TAB_SHORTS"),
|
SHORTS("TAB_SHORTS"),
|
||||||
/**
|
/**
|
||||||
* Create new video tab.
|
* Create new video tab.
|
||||||
*
|
* This tab will never be in a selected state, even if the create video UI is on screen.
|
||||||
* {@link #isSelected()} always returns false, even if the create video UI is on screen.
|
|
||||||
*/
|
*/
|
||||||
CREATE("CREATION_TAB_LARGE"),
|
CREATE("CREATION_TAB_LARGE"),
|
||||||
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS"),
|
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS"),
|
||||||
@ -145,41 +277,43 @@ public final class NavigationBar {
|
|||||||
// The hooked YT code does not use an enum, and a dummy name is used here.
|
// The hooked YT code does not use an enum, and a dummy name is used here.
|
||||||
LIBRARY_YOU("YOU_LIBRARY_DUMMY_PLACEHOLDER_NAME");
|
LIBRARY_YOU("YOU_LIBRARY_DUMMY_PLACEHOLDER_NAME");
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static volatile NavigationButton selectedNavigationButton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This will return null only if the currently selected tab is unknown.
|
||||||
|
* This scenario will only happen if the UI has different tabs due to an A/B user test
|
||||||
|
* or YT abruptly changes the navigation layout for some other reason.
|
||||||
|
*
|
||||||
|
* All code calling this method should handle a null return value.
|
||||||
|
*
|
||||||
|
* <b>Due to issues with how YT processes physical back button events,
|
||||||
|
* this patch uses workarounds that can cause this method to take up to 75ms
|
||||||
|
* if the device back button was recently pressed.</b>
|
||||||
|
*
|
||||||
* @return The active navigation tab.
|
* @return The active navigation tab.
|
||||||
* If the user is in the create new video UI, this returns NULL.
|
* If the user is in the upload video UI, this returns tab that is still visually
|
||||||
|
* selected on screen (whatever tab the user was on before tapping the upload button).
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static NavigationButton getSelectedNavigationButton() {
|
public static NavigationButton getSelectedNavigationButton() {
|
||||||
for (NavigationButton button : values()) {
|
waitForNavButtonLatchIfNeeded();
|
||||||
if (button.isSelected()) return button;
|
return selectedNavigationButton;
|
||||||
}
|
|
||||||
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.
|
* YouTube enum name for this tab.
|
||||||
*/
|
*/
|
||||||
private final String ytEnumName;
|
private final String ytEnumName;
|
||||||
private volatile WeakReference<ImageView> imageViewRef = new WeakReference<>(null);
|
|
||||||
|
|
||||||
NavigationButton(String ytEnumName) {
|
NavigationButton(String ytEnumName) {
|
||||||
this.ytEnumName = ytEnumName;
|
this.ytEnumName = ytEnumName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSelected() {
|
public boolean isLibraryOrYouTab() {
|
||||||
ImageView view = imageViewRef.get();
|
return this == LIBRARY_YOU || this == LIBRARY_PIVOT_UNKNOWN
|
||||||
return view != null && view.isSelected();
|
|| this == LIBRARY_OLD_UI || this == LIBRARY_INCOGNITO
|
||||||
|
|| this == LIBRARY_LOGGED_OUT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user