Skip to content
This repository has been archived by the owner on Oct 26, 2024. It is now read-only.

Commit

Permalink
feat(YouTube - Hide layout components): Filter home/search results by…
Browse files Browse the repository at this point in the history
… keywords (#584)

Co-authored-by: oSumAtrIX <[email protected]>
  • Loading branch information
LisoUseInAIKyrios and oSumAtrIX authored Mar 27, 2024
1 parent 2a56cc0 commit 0222a4a
Show file tree
Hide file tree
Showing 12 changed files with 563 additions and 109 deletions.
19 changes: 13 additions & 6 deletions app/src/main/java/app/revanced/integrations/shared/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,18 +196,29 @@ public static float getResourceDimension(@NonNull String resourceIdentifierName)
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
}

public interface MatchFilter<T> {
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 extends View> T getChildView(@NonNull ViewGroup viewGroup, @NonNull MatchFilter filter) {
public static <T extends View> T getChildView(@NonNull ViewGroup viewGroup, boolean searchRecursively,
@NonNull MatchFilter<View> 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;
}
Expand All @@ -223,10 +234,6 @@ public static void restartApp(@NonNull Context context) {
System.exit(0);
}

public interface MatchFilter<T> {
boolean matches(T object);
}

public static Context getContext() {
if (context == null) {
Logger.initializationException(Utils.class, "Context is null, returning null!", null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package app.revanced.integrations.youtube;

import androidx.annotation.NonNull;

import java.nio.charset.StandardCharsets;

public final class ByteTrieSearch extends TrieSearch<byte[]> {

private static final class ByteTrieNode extends TrieNode<byte[]> {
Expand All @@ -24,18 +28,18 @@ int getTextLength(byte[] text) {
}

/**
* @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);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package app.revanced.integrations.youtube;

import androidx.annotation.NonNull;

/**
* Text pattern searching using a prefix tree (trie).
*/
Expand All @@ -26,19 +28,7 @@ int getTextLength(String text) {
}
}

/**
* @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);
}
}
56 changes: 21 additions & 35 deletions app/src/main/java/app/revanced/integrations/youtube/TrieSearch.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
/**
* 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<T> {

Expand Down Expand Up @@ -45,14 +42,14 @@ public interface TriePatternMatchedCallback<T> {
*/
private static final class TrieCompressedPath<T> {
final T pattern;
final int patternLength;
final int patternStartIndex;
final int patternLength;
final TriePatternMatchedCallback<T> callback;

TrieCompressedPath(T pattern, int patternLength, int patternStartIndex, TriePatternMatchedCallback<T> callback) {
TrieCompressedPath(T pattern, int patternStartIndex, int patternLength, TriePatternMatchedCallback<T> callback) {
this.pattern = pattern;
this.patternLength = patternLength;
this.patternStartIndex = patternStartIndex;
this.patternLength = patternLength;
this.callback = callback;
}
boolean matches(TrieNode<T> enclosingNode, // Used only for the get character method.
Expand All @@ -76,19 +73,10 @@ static abstract class TrieNode<T> {
*/
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.
Expand Down Expand Up @@ -144,11 +132,11 @@ static boolean isInvalidRange(char character) {

/**
* @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<T> callback) {
if (patternIndex == patternLength) { // Reached the end of the pattern.
if (endOfPatternCallback == null) {
Expand All @@ -165,16 +153,13 @@ private void addPattern(@NonNull T pattern, int patternLength, int patternIndex,
children = new TrieNode[1];
TrieCompressedPath<T> 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<T> child = children[arrayIndex];
if (child == null) {
Expand All @@ -185,12 +170,11 @@ private void addPattern(@NonNull T pattern, int patternLength, int patternIndex,
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<T> child) {
int replacementArraySize = Objects.requireNonNull(children).length;
Expand All @@ -209,7 +193,6 @@ private void expandChildArray(TrieNode<T> child) {
}
}
if (collision) {
if (replacementArraySize > CHILDREN_ARRAY_MAX_SIZE) throw new IllegalStateException();
continue;
}
children = replacement;
Expand All @@ -232,22 +215,23 @@ private static int hashIndexForTableSize(int arraySize, char nodeValue) {

/**
* 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 <T> boolean matches(final TrieNode<T> startNode, final T searchText, final int searchTextLength,
int searchTextIndex, final Object callbackParameter) {
private static <T> boolean matches(final TrieNode<T> startNode, final T searchText,
int searchTextIndex, final int searchTextEndIndex,
final Object callbackParameter) {
TrieNode<T> node = startNode;
int currentMatchLength = 0;

while (true) {
TrieCompressedPath<T> 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<TriePatternMatchedCallback<T>> endOfPatternCallback = node.endOfPatternCallback;
Expand All @@ -266,7 +250,7 @@ private static <T> boolean matches(final TrieNode<T> startNode, final T searchTe
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.
}

Expand Down Expand Up @@ -323,8 +307,10 @@ private int estimatedNumberOfPointersUsed() {
*/
private final List<T> patterns = new ArrayList<>();

TrieSearch(@NonNull TrieNode<T> root) {
@SafeVarargs
TrieSearch(@NonNull TrieNode<T> root, @NonNull T... patterns) {
this.root = Objects.requireNonNull(root);
addPatterns(patterns);
}

@SafeVarargs
Expand Down Expand Up @@ -355,7 +341,7 @@ void addPattern(@NonNull T pattern, int patternLength, @Nullable TriePatternMatc
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) {
Expand Down Expand Up @@ -398,7 +384,7 @@ private boolean matches(@NonNull T textToSearch, int textToSearchLength, int sta
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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

public static boolean switchCreateWithNotificationButton() {
return Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
}
private static final Map<NavigationButton, Boolean> 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());
}
};

public static void hideButton(final View buttonView) {
if (lastNavigationButton == null) return;
private static final Boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();

for (NavigationButton button : NavigationButton.values())
if (button.name.equals(lastNavigationButton.name()))
if (button.enabled) buttonView.setVisibility(View.GONE);
/**
* Injection point.
*/
public static boolean switchCreateWithNotificationButton() {
return SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON;
}

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);
}
}
}
Loading

0 comments on commit 0222a4a

Please sign in to comment.