fix(Spotify): Hide Create button
patch failing in edge cases (#5131)
This commit is contained in:
@ -3,23 +3,29 @@ package app.revanced.extension.spotify.layout.hide.createbutton;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.spotify.shared.ComponentFilters.*;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class HideCreateButtonPatch {
|
public final class HideCreateButtonPatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of ids of resources which contain the Create button title.
|
* A list of component filters that match whether a navigation bar item is the Create button.
|
||||||
|
* The main approach used is matching the resource id for the Create button title.
|
||||||
*/
|
*/
|
||||||
private static final List<String> CREATE_BUTTON_TITLE_RES_ID_LIST = List.of(
|
private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of(
|
||||||
Integer.toString(Utils.getResourceIdentifier("navigationbar_musicappitems_create_title", "string"))
|
new ResourceIdComponentFilter("navigationbar_musicappitems_create_title", "string"),
|
||||||
|
// Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded,
|
||||||
|
// and thus getting the resource identifier for the Create button title always return 0.
|
||||||
|
// FIXME: Remove this once the above issue is no longer relevant.
|
||||||
|
new StringComponentFilter("spotify:create-menu")
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The old id of the resource which contained the Create button title. Used in older versions of the app.
|
* A component filter for the old id of the resource which contained the Create button title.
|
||||||
|
* Used in older versions of the app.
|
||||||
*/
|
*/
|
||||||
private static final int OLD_CREATE_BUTTON_TITLE_RES_ID =
|
private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER =
|
||||||
Utils.getResourceIdentifier("bottom_navigation_bar_create_tab_title", "string");
|
new ResourceIdComponentFilter("bottom_navigation_bar_create_tab_title", "string");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. This method is called on every navigation bar item to check whether it is the Create button.
|
* Injection point. This method is called on every navigation bar item to check whether it is the Create button.
|
||||||
@ -33,28 +39,20 @@ public final class HideCreateButtonPatch {
|
|||||||
|
|
||||||
String stringifiedNavigationBarItem = navigationBarItem.toString();
|
String stringifiedNavigationBarItem = navigationBarItem.toString();
|
||||||
|
|
||||||
boolean isCreateButton = false;
|
for (ComponentFilter componentFilter : CREATE_BUTTON_COMPONENT_FILTERS) {
|
||||||
String matchedTitleResId = null;
|
if (componentFilter.filterUnavailable()) {
|
||||||
|
Logger.printInfo(() -> "returnNullIfIsCreateButton: Filter " +
|
||||||
for (String titleResId : CREATE_BUTTON_TITLE_RES_ID_LIST) {
|
componentFilter.getFilterRepresentation() + " not available, skipping");
|
||||||
// In case the resource id has not been found.
|
|
||||||
if (titleResId.equals("0")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stringifiedNavigationBarItem.contains(titleResId)) {
|
if (stringifiedNavigationBarItem.contains(componentFilter.getFilterValue())) {
|
||||||
isCreateButton = true;
|
Logger.printInfo(() -> "Hiding Create button because the navigation bar item " + navigationBarItem +
|
||||||
matchedTitleResId = titleResId;
|
" matched the filter " + componentFilter.getFilterRepresentation());
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCreateButton) {
|
|
||||||
String finalMatchedTitleResId = matchedTitleResId;
|
|
||||||
Logger.printInfo(() -> "Hiding Create button because the navigation bar item " + navigationBarItem +
|
|
||||||
" matched the title resource id " + finalMatchedTitleResId);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return navigationBarItem;
|
return navigationBarItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,16 +61,15 @@ public final class HideCreateButtonPatch {
|
|||||||
* Create button.
|
* Create button.
|
||||||
*/
|
*/
|
||||||
public static boolean isOldCreateButton(int oldNavigationBarItemTitleResId) {
|
public static boolean isOldCreateButton(int oldNavigationBarItemTitleResId) {
|
||||||
// In case the resource id has not been found.
|
if (OLD_CREATE_BUTTON_COMPONENT_FILTER.filterUnavailable()) {
|
||||||
if (OLD_CREATE_BUTTON_TITLE_RES_ID == 0) {
|
Logger.printInfo(() -> "Skipping hiding old Create button because the resource id for " +
|
||||||
|
OLD_CREATE_BUTTON_COMPONENT_FILTER.resourceName + " is not available");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isCreateButton = oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_TITLE_RES_ID;
|
if (oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_COMPONENT_FILTER.getResourceId()) {
|
||||||
|
|
||||||
if (isCreateButton) {
|
|
||||||
Logger.printInfo(() -> "Hiding old Create button because the navigation bar item title resource id" +
|
Logger.printInfo(() -> "Hiding old Create button because the navigation bar item title resource id" +
|
||||||
" matched " + OLD_CREATE_BUTTON_TITLE_RES_ID);
|
" matched " + OLD_CREATE_BUTTON_COMPONENT_FILTER.getFilterRepresentation());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package app.revanced.extension.spotify.misc;
|
|||||||
import static java.lang.Boolean.FALSE;
|
import static java.lang.Boolean.FALSE;
|
||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
|
|
||||||
|
import app.revanced.extension.spotify.shared.ComponentFilters.*;
|
||||||
import com.spotify.home.evopage.homeapi.proto.Section;
|
import com.spotify.home.evopage.homeapi.proto.Section;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -11,7 +12,6 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class UnlockPremiumPatch {
|
public final class UnlockPremiumPatch {
|
||||||
@ -104,29 +104,22 @@ public final class UnlockPremiumPatch {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of lists which contain strings that match whether a context menu item should be filtered out.
|
* A list of lists which contain component filters that match whether a context menu item should be filtered out.
|
||||||
* The main approach used is matching context menu items by the id of their text resource.
|
* The main approach used is matching context menu items by the id of their title resource.
|
||||||
*/
|
*/
|
||||||
private static final List<List<String>> FILTERED_CONTEXT_MENU_ITEMS_BY_STRINGS = List.of(
|
private static final List<List<ComponentFilter>> CONTEXT_MENU_ITEMS_COMPONENT_FILTERS = List.of(
|
||||||
// "Listen to music ad-free" upsell on playlists.
|
// "Listen to music ad-free" upsell on playlists.
|
||||||
List.of(getResourceIdentifier("context_menu_remove_ads")),
|
List.of(new ResourceIdComponentFilter("context_menu_remove_ads", "id")),
|
||||||
// "Listen to music ad-free" upsell on albums.
|
// "Listen to music ad-free" upsell on albums.
|
||||||
List.of(getResourceIdentifier("playlist_entity_reinventfree_adsfree_context_menu_item")),
|
List.of(new ResourceIdComponentFilter("playlist_entity_reinventfree_adsfree_context_menu_item", "id")),
|
||||||
// "Start a Jam" context menu item, but only filtered if the user does not have premium and the item is
|
// "Start a Jam" context menu item, but only filtered if the user does not have premium and the item is
|
||||||
// being used as a Premium upsell (ad).
|
// being used as a Premium upsell (ad).
|
||||||
List.of(
|
List.of(
|
||||||
getResourceIdentifier("group_session_context_menu_start"),
|
new ResourceIdComponentFilter("group_session_context_menu_start", "id"),
|
||||||
"isPremiumUpsell=true"
|
new StringComponentFilter("isPremiumUpsell=true")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility method for returning resources ids as strings.
|
|
||||||
*/
|
|
||||||
private static String getResourceIdentifier(String resourceIdentifierName) {
|
|
||||||
return Integer.toString(Utils.getResourceIdentifier(resourceIdentifierName, "id"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Override account attributes.
|
* Injection point. Override account attributes.
|
||||||
*/
|
*/
|
||||||
@ -210,32 +203,34 @@ public final class UnlockPremiumPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String stringifiedContextMenuItem = contextMenuItem.toString();
|
String stringifiedContextMenuItem = contextMenuItem.toString();
|
||||||
for (List<String> stringList : FILTERED_CONTEXT_MENU_ITEMS_BY_STRINGS) {
|
|
||||||
|
for (List<ComponentFilter> componentFilters : CONTEXT_MENU_ITEMS_COMPONENT_FILTERS) {
|
||||||
boolean allMatch = true;
|
boolean allMatch = true;
|
||||||
StringBuilder matchedStrings = new StringBuilder();
|
StringBuilder matchedFilterRepresentations = new StringBuilder();
|
||||||
|
|
||||||
for (int i = 0; i < stringList.size(); i++) {
|
for (int i = 0, filterSize = componentFilters.size(); i < filterSize; i++) {
|
||||||
String string = stringList.get(i);
|
ComponentFilter componentFilter = componentFilters.get(i);
|
||||||
|
|
||||||
// In case the string is a resource id, and it has not been found.
|
if (componentFilter.filterUnavailable()) {
|
||||||
if (string.equals("0")) {
|
Logger.printInfo(() -> "isFilteredContextMenuItem: Filter " +
|
||||||
|
componentFilter.getFilterRepresentation() + " not available, skipping");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stringifiedContextMenuItem.contains(string)) {
|
if (!stringifiedContextMenuItem.contains(componentFilter.getFilterValue())) {
|
||||||
allMatch = false;
|
allMatch = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
matchedStrings.append(string);
|
matchedFilterRepresentations.append(componentFilter.getFilterRepresentation());
|
||||||
if (i < stringList.size() - 1) {
|
if (i < filterSize - 1) {
|
||||||
matchedStrings.append(", ");
|
matchedFilterRepresentations.append(", ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allMatch) {
|
if (allMatch) {
|
||||||
Logger.printInfo(() -> "Filtering context menu item " + stringifiedContextMenuItem +
|
Logger.printInfo(() -> "Filtering context menu item " + stringifiedContextMenuItem +
|
||||||
" because the following strings matched: " + matchedStrings);
|
" because the following filters matched: " + matchedFilterRepresentations);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
package app.revanced.extension.spotify.shared;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
public final class ComponentFilters {
|
||||||
|
|
||||||
|
public interface ComponentFilter {
|
||||||
|
String getFilterValue();
|
||||||
|
String getFilterRepresentation();
|
||||||
|
default boolean filterUnavailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ResourceIdComponentFilter implements ComponentFilter {
|
||||||
|
|
||||||
|
public final String resourceName;
|
||||||
|
public final String resourceType;
|
||||||
|
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
|
||||||
|
// 0 is returned when a resource has not been found.
|
||||||
|
private int resourceId = -1;
|
||||||
|
private String stringfiedResourceId = null;
|
||||||
|
|
||||||
|
public ResourceIdComponentFilter(String resourceName, String resourceType) {
|
||||||
|
this.resourceName = resourceName;
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResourceId() {
|
||||||
|
if (resourceId == -1) {
|
||||||
|
resourceId = Utils.getResourceIdentifier(resourceName, resourceType);
|
||||||
|
}
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilterValue() {
|
||||||
|
if (stringfiedResourceId == null) {
|
||||||
|
stringfiedResourceId = Integer.toString(getResourceId());
|
||||||
|
}
|
||||||
|
return stringfiedResourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilterRepresentation() {
|
||||||
|
boolean resourceFound = getResourceId() != 0;
|
||||||
|
return (resourceFound ? getFilterValue() + " (" : "") + resourceName + (resourceFound ? ")" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean filterUnavailable() {
|
||||||
|
boolean resourceNotFound = getResourceId() == 0;
|
||||||
|
if (resourceNotFound) {
|
||||||
|
Logger.printInfo(() -> "Resource id for " + resourceName + " was not found");
|
||||||
|
}
|
||||||
|
return resourceNotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class StringComponentFilter implements ComponentFilter {
|
||||||
|
|
||||||
|
public final String string;
|
||||||
|
|
||||||
|
public StringComponentFilter(String string) {
|
||||||
|
this.string = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilterValue() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilterRepresentation() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user