feat(Custom branding): Add in-app settings to change icon and name (#6059)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
@@ -31,9 +31,6 @@ import app.revanced.extension.shared.ui.CustomDialog;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class GmsCoreSupport {
|
||||
private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube";
|
||||
private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music";
|
||||
|
||||
private static final String GMS_CORE_PACKAGE_NAME
|
||||
= getGmsCoreVendorGroupId() + ".android.gms";
|
||||
private static final Uri GMS_CORE_PROVIDER
|
||||
@@ -53,6 +50,20 @@ public class GmsCoreSupport {
|
||||
@Nullable
|
||||
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
|
||||
|
||||
private static String getOriginalPackageName() {
|
||||
return null; // Modified during patching.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the current package name is the same as the original unpatched app.
|
||||
* If `GmsCore support` was not included during patching, this returns true;
|
||||
*/
|
||||
public static boolean isPackageNameOriginal() {
|
||||
String originalPackageName = getOriginalPackageName();
|
||||
return originalPackageName == null
|
||||
|| originalPackageName.equals(Utils.getContext().getPackageName());
|
||||
}
|
||||
|
||||
private static void open(String queryOrLink) {
|
||||
Logger.printInfo(() -> "Opening link: " + queryOrLink);
|
||||
|
||||
@@ -113,11 +124,10 @@ public class GmsCoreSupport {
|
||||
// Verify the user has not included GmsCore for a root installation.
|
||||
// GmsCore Support changes the package name, but with a mounted installation
|
||||
// all manifest changes are ignored and the original package name is used.
|
||||
String packageName = context.getPackageName();
|
||||
if (packageName.equals(PACKAGE_NAME_YOUTUBE) || packageName.equals(PACKAGE_NAME_YOUTUBE_MUSIC)) {
|
||||
if (isPackageNameOriginal()) {
|
||||
Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included");
|
||||
// Cannot use localize text here, since the app will load
|
||||
// resources from the unpatched app and all patch strings are missing.
|
||||
// Cannot use localize text here, since the app will load resources
|
||||
// from the unpatched app and all patch strings are missing.
|
||||
Utils.showToastLong("The 'GmsCore support' patch breaks mount installations");
|
||||
|
||||
// Do not exit. If the app exits before launch completes (and without
|
||||
@@ -250,8 +260,8 @@ public class GmsCoreSupport {
|
||||
};
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
private static String getGmsCoreVendorGroupId() {
|
||||
return "app.revanced";
|
||||
// Modified during patching.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package app.revanced.extension.shared.patches;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.GmsCoreSupport;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
|
||||
/**
|
||||
* Patch shared by YouTube and YT Music.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class CustomBrandingPatch {
|
||||
|
||||
// Important: In the future, additional branding themes can be added but all existing and prior
|
||||
// themes cannot be removed or renamed.
|
||||
//
|
||||
// This is because if a user has a branding theme selected, then only that launch alias is enabled.
|
||||
// If a future update removes or renames that alias, then after updating the app is effectively
|
||||
// broken and it cannot be opened and not even clearing the app data will fix it.
|
||||
// In that situation the only fix is to completely uninstall and reinstall again.
|
||||
//
|
||||
// The most that can be done is to hide a theme from the UI and keep the alias with dummy data.
|
||||
public enum BrandingTheme {
|
||||
/**
|
||||
* Original unpatched icon. Must be first enum.
|
||||
*/
|
||||
ORIGINAL("revanced_original"),
|
||||
ROUNDED("revanced_rounded"),
|
||||
MINIMAL("revanced_minimal"),
|
||||
SCALED("revanced_scaled"),
|
||||
/**
|
||||
* User provided custom icon. Must be the last enum.
|
||||
*/
|
||||
CUSTOM("revanced_custom");
|
||||
|
||||
public final String themeAlias;
|
||||
|
||||
BrandingTheme(String themeAlias) {
|
||||
this.themeAlias = themeAlias;
|
||||
}
|
||||
|
||||
private String packageAndNameIndexToClassAlias(String packageName, int appIndex) {
|
||||
if (appIndex <= 0) {
|
||||
throw new IllegalArgumentException("App index starts at index 1");
|
||||
}
|
||||
return packageName + '.' + themeAlias + '_' + appIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* The total number of app name aliases, including dummy aliases.
|
||||
*/
|
||||
private static int numberOfPresetAppNames() {
|
||||
// Modified during patching.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static void setBranding() {
|
||||
try {
|
||||
if (GmsCoreSupport.isPackageNameOriginal()) {
|
||||
Logger.printInfo(() -> "App is root mounted. Cannot dynamically change app icon");
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = Utils.getContext();
|
||||
PackageManager pm = context.getPackageManager();
|
||||
String packageName = context.getPackageName();
|
||||
|
||||
BrandingTheme selectedBranding = BaseSettings.CUSTOM_BRANDING_ICON.get();
|
||||
final int selectedNameIndex = BaseSettings.CUSTOM_BRANDING_NAME.get();
|
||||
ComponentName componentToEnable = null;
|
||||
ComponentName defaultComponent = null;
|
||||
List<ComponentName> componentsToDisable = new ArrayList<>();
|
||||
|
||||
for (BrandingTheme theme : BrandingTheme.values()) {
|
||||
// Must always update all aliases including custom alias (last index).
|
||||
final int numberOfPresetAppNames = numberOfPresetAppNames();
|
||||
|
||||
// App name indices starts at 1.
|
||||
for (int index = 1; index <= numberOfPresetAppNames; index++) {
|
||||
String aliasClass = theme.packageAndNameIndexToClassAlias(packageName, index);
|
||||
ComponentName component = new ComponentName(packageName, aliasClass);
|
||||
if (defaultComponent == null) {
|
||||
// Default is always the first alias.
|
||||
defaultComponent = component;
|
||||
}
|
||||
|
||||
if (index == selectedNameIndex && theme == selectedBranding) {
|
||||
componentToEnable = component;
|
||||
} else {
|
||||
componentsToDisable.add(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (componentToEnable == null) {
|
||||
// User imported a bad app name index value. Either the imported data
|
||||
// was corrupted, or they previously had custom name enabled and the app
|
||||
// no longer has a custom name specified.
|
||||
Utils.showToastLong("Custom branding reset");
|
||||
BaseSettings.CUSTOM_BRANDING_ICON.resetToDefault();
|
||||
BaseSettings.CUSTOM_BRANDING_NAME.resetToDefault();
|
||||
|
||||
componentToEnable = defaultComponent;
|
||||
componentsToDisable.remove(defaultComponent);
|
||||
}
|
||||
|
||||
for (ComponentName disable : componentsToDisable) {
|
||||
// Use info logging because if the alias status become corrupt the app cannot launch.
|
||||
Logger.printInfo(() -> "Disabling: " + disable.getClassName());
|
||||
pm.setComponentEnabledSetting(disable,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
|
||||
ComponentName componentToEnableFinal = componentToEnable;
|
||||
Logger.printInfo(() -> "Enabling: " + componentToEnableFinal.getClassName());
|
||||
pm.setComponentEnabledSetting(componentToEnable,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setBranding failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package app.revanced.extension.shared.settings;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
|
||||
|
||||
@@ -40,4 +41,7 @@ public class BaseSettings {
|
||||
public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE);
|
||||
|
||||
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
||||
|
||||
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", BrandingTheme.ORIGINAL, true);
|
||||
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
@@ -125,6 +126,8 @@ public class ReVancedAboutPreference extends Preference {
|
||||
|
||||
{
|
||||
setOnPreferenceClickListener(pref -> {
|
||||
Context context = pref.getContext();
|
||||
|
||||
// Show a progress spinner if the social links are not fetched yet.
|
||||
if (!AboutLinksRoutes.hasFetchedLinks() && Utils.isNetworkConnected()) {
|
||||
// Show a progress spinner, but only if the api fetch takes more than a half a second.
|
||||
@@ -137,17 +140,18 @@ public class ReVancedAboutPreference extends Preference {
|
||||
handler.postDelayed(showDialogRunnable, delayToShowProgressSpinner);
|
||||
|
||||
Utils.runOnBackgroundThread(() ->
|
||||
fetchLinksAndShowDialog(handler, showDialogRunnable, progress));
|
||||
fetchLinksAndShowDialog(context, handler, showDialogRunnable, progress));
|
||||
} else {
|
||||
// No network call required and can run now.
|
||||
fetchLinksAndShowDialog(null, null, null);
|
||||
fetchLinksAndShowDialog(context, null, null, null);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchLinksAndShowDialog(@Nullable Handler handler,
|
||||
private void fetchLinksAndShowDialog(Context context,
|
||||
@Nullable Handler handler,
|
||||
Runnable showDialogRunnable,
|
||||
@Nullable ProgressDialog progress) {
|
||||
WebLink[] links = AboutLinksRoutes.fetchAboutLinks();
|
||||
@@ -164,7 +168,17 @@ public class ReVancedAboutPreference extends Preference {
|
||||
if (handler != null) {
|
||||
handler.removeCallbacks(showDialogRunnable);
|
||||
}
|
||||
if (progress != null) {
|
||||
|
||||
// Don't continue if the activity is done. To test this tap the
|
||||
// about dialog and immediately press back before the dialog can show.
|
||||
if (context instanceof Activity activity) {
|
||||
if (activity.isFinishing() || activity.isDestroyed()) {
|
||||
Logger.printDebug(() -> "Not showing about dialog, activity is closed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (progress != null && progress.isShowing()) {
|
||||
progress.dismiss();
|
||||
}
|
||||
new WebViewDialog(getContext(), htmlDialog).show();
|
||||
|
||||
@@ -1919,6 +1919,7 @@ public final class app/revanced/util/ResourceUtilsKt {
|
||||
public static final fun forEachChildElement (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
|
||||
public static final fun insertFirst (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)V
|
||||
public static final fun iterateXmlNodeChildren (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
|
||||
public static final fun removeFromParent (Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||
}
|
||||
|
||||
public final class app/revanced/util/resource/ArrayResource : app/revanced/util/resource/BaseResource {
|
||||
|
||||
@@ -4,6 +4,10 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_MAIN_ACTIVITY_NAME
|
||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
||||
import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.shared.layout.branding.baseCustomBrandingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
@@ -50,24 +54,18 @@ private val disableSplashAnimationPatch = bytecodePatch {
|
||||
}
|
||||
}
|
||||
|
||||
private const val APP_NAME = "YT Music ReVanced"
|
||||
|
||||
@Suppress("unused")
|
||||
val customBrandingPatch = baseCustomBrandingPatch(
|
||||
defaultAppName = APP_NAME,
|
||||
appNameValues = mapOf(
|
||||
"YT Music ReVanced" to APP_NAME,
|
||||
"Music ReVanced" to "Music ReVanced",
|
||||
"Music" to "Music",
|
||||
"YT Music" to "YT Music",
|
||||
),
|
||||
resourceFolder = "custom-branding/music",
|
||||
iconResourceFileNames = arrayOf(
|
||||
"adaptiveproduct_youtube_music_2024_q4_background_color_108",
|
||||
"adaptiveproduct_youtube_music_2024_q4_foreground_color_108",
|
||||
"ic_launcher_release",
|
||||
),
|
||||
monochromeIconFileNames = arrayOf("ic_app_icons_themed_youtube_music.xml"),
|
||||
addResourcePatchName = "music",
|
||||
originalLauncherIconName = "ic_launcher_release",
|
||||
originalAppName = "@string/app_launcher_name",
|
||||
originalAppPackageName = MUSIC_PACKAGE_NAME,
|
||||
copyExistingIntentsToAliases = false,
|
||||
numberOfPresetAppNames = 5,
|
||||
mainActivityOnCreateFingerprint = musicActivityOnCreateFingerprint,
|
||||
mainActivityName = MUSIC_MAIN_ACTIVITY_NAME,
|
||||
activityAliasNameWithIntents = MUSIC_MAIN_ACTIVITY_NAME,
|
||||
preferenceScreen = PreferenceScreen.GENERAL,
|
||||
|
||||
block = {
|
||||
dependsOn(disableSplashAnimationPatch)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package app.revanced.patches.music.misc.gms
|
||||
|
||||
object Constants {
|
||||
internal const val MUSIC_MAIN_ACTIVITY_NAME = "com.google.android.apps.youtube.music.activities.MusicActivity"
|
||||
|
||||
internal const val REVANCED_MUSIC_PACKAGE_NAME = "app.revanced.android.apps.youtube.music"
|
||||
internal const val MUSIC_PACKAGE_NAME = "com.google.android.apps.youtube.music"
|
||||
}
|
||||
|
||||
@@ -1,173 +1,387 @@
|
||||
package app.revanced.patches.shared.layout.branding
|
||||
|
||||
import app.revanced.patcher.Fingerprint
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.ResourcePatchBuilder
|
||||
import app.revanced.patcher.patch.ResourcePatchContext
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.Utils.trimIndentMultiline
|
||||
import app.revanced.util.copyResources
|
||||
import app.revanced.util.findElementByAttributeValueOrThrow
|
||||
import app.revanced.util.removeFromParent
|
||||
import app.revanced.util.returnEarly
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.NodeList
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.util.logging.Logger
|
||||
|
||||
private const val REVANCED_ICON = "ReVanced*Logo" // Can never be a valid path.
|
||||
|
||||
internal val mipmapDirectories = arrayOf(
|
||||
private val mipmapDirectories = arrayOf(
|
||||
// Target app does not have ldpi icons.
|
||||
"mdpi",
|
||||
"hdpi",
|
||||
"xhdpi",
|
||||
"xxhdpi",
|
||||
"xxxhdpi",
|
||||
).map { "mipmap-$it" }.toTypedArray()
|
||||
"mipmap-mdpi",
|
||||
"mipmap-hdpi",
|
||||
"mipmap-xhdpi",
|
||||
"mipmap-xxhdpi",
|
||||
"mipmap-xxxhdpi"
|
||||
)
|
||||
|
||||
private fun formatResourceFileList(resourceNames: Array<String>) = resourceNames.joinToString("\n") { "- $it" }
|
||||
private val iconStyleNames = arrayOf(
|
||||
"rounded",
|
||||
"minimal",
|
||||
"scaled"
|
||||
)
|
||||
|
||||
/**
|
||||
* Attempts to fix unescaped and invalid characters not allowed for an Android app name.
|
||||
*/
|
||||
private fun escapeAppName(name: String): String? {
|
||||
// Remove ASCII control characters.
|
||||
val cleanedName = name.filter { it.code >= 32 }
|
||||
private const val ORIGINAL_USER_ICON_STYLE_NAME = "original"
|
||||
private const val CUSTOM_USER_ICON_STYLE_NAME = "custom"
|
||||
|
||||
// Replace invalid XML characters with escaped equivalents.
|
||||
val escapedName = cleanedName
|
||||
.replace("&", "&") // Must be first to avoid double-escaping.
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace(Regex("(?<!&)\""), """)
|
||||
private const val LAUNCHER_RESOURCE_NAME_PREFIX = "revanced_launcher_"
|
||||
private const val LAUNCHER_ADAPTIVE_BACKGROUND_PREFIX = "revanced_adaptive_background_"
|
||||
private const val LAUNCHER_ADAPTIVE_FOREGROUND_PREFIX = "revanced_adaptive_foreground_"
|
||||
private const val LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX = "revanced_adaptive_monochrome_"
|
||||
|
||||
// Trim empty spacing.
|
||||
val trimmed = escapedName.trim()
|
||||
private val USER_CUSTOM_ADAPTIVE_FILE_NAMES = arrayOf(
|
||||
"$LAUNCHER_ADAPTIVE_BACKGROUND_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.png",
|
||||
"$LAUNCHER_ADAPTIVE_FOREGROUND_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.png"
|
||||
)
|
||||
|
||||
return trimmed.ifBlank { null }
|
||||
}
|
||||
private const val USER_CUSTOM_MONOCHROME_NAME = "$LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.xml"
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/patches/CustomBrandingPatch;"
|
||||
|
||||
/**
|
||||
* Shared custom branding patch for YouTube and YT Music.
|
||||
*/
|
||||
internal fun baseCustomBrandingPatch(
|
||||
defaultAppName: String,
|
||||
appNameValues: Map<String, String>,
|
||||
resourceFolder: String,
|
||||
iconResourceFileNames: Array<String>,
|
||||
monochromeIconFileNames: Array<String>,
|
||||
block: ResourcePatchBuilder.() -> Unit = {},
|
||||
addResourcePatchName: String,
|
||||
originalLauncherIconName: String,
|
||||
originalAppName: String,
|
||||
originalAppPackageName: String,
|
||||
copyExistingIntentsToAliases: Boolean,
|
||||
numberOfPresetAppNames: Int,
|
||||
mainActivityOnCreateFingerprint: Fingerprint,
|
||||
mainActivityName: String,
|
||||
activityAliasNameWithIntents: String,
|
||||
preferenceScreen: BasePreferenceScreen.Screen,
|
||||
block: ResourcePatchBuilder.() -> Unit,
|
||||
executeBlock: ResourcePatchContext.() -> Unit = {}
|
||||
): ResourcePatch = resourcePatch(
|
||||
name = "Custom branding",
|
||||
description = "Applies a custom app name and icon. Defaults to \"$defaultAppName\" and the ReVanced logo.",
|
||||
use = false,
|
||||
description = "Adds options to change the app icon and app name. " +
|
||||
"Branding cannot be changed for mounted (root) installations."
|
||||
) {
|
||||
val iconResourceFileNamesPng = iconResourceFileNames.map { "$it.png" }.toTypedArray<String>()
|
||||
|
||||
val appName by stringOption(
|
||||
key = "appName",
|
||||
default = defaultAppName,
|
||||
values = appNameValues,
|
||||
val customName by stringOption(
|
||||
key = "customName",
|
||||
title = "App name",
|
||||
description = "The name of the app.",
|
||||
description = "Custom app name."
|
||||
)
|
||||
|
||||
val iconPath by stringOption(
|
||||
key = "iconPath",
|
||||
default = REVANCED_ICON,
|
||||
values = mapOf("ReVanced Logo" to REVANCED_ICON),
|
||||
title = "App icon",
|
||||
val customIcon by stringOption(
|
||||
key = "customIcon",
|
||||
title = "Custom icon",
|
||||
description = """
|
||||
The icon to apply to the app.
|
||||
Folder with images to use as a custom icon.
|
||||
|
||||
If a path to a folder is provided, the folder must contain the following folders:
|
||||
|
||||
${formatResourceFileList(mipmapDirectories)}
|
||||
|
||||
Each of these folders must contain the following files:
|
||||
|
||||
${formatResourceFileList(iconResourceFileNamesPng)}
|
||||
The folder must contain one or more of the following folders, depending on the DPI of the device:
|
||||
${mipmapDirectories.joinToString("\n") { "- $it" }}
|
||||
|
||||
Optionally, a 'drawable' folder with the monochrome icon files:
|
||||
|
||||
${formatResourceFileList(monochromeIconFileNames)}
|
||||
""".trimIndentMultiline(),
|
||||
Each of the folders must contain all of the following files:
|
||||
${USER_CUSTOM_ADAPTIVE_FILE_NAMES.joinToString("\n")}
|
||||
|
||||
Optionally, the path can contain a 'drawable' folder with the monochrome icon file:
|
||||
$USER_CUSTOM_MONOCHROME_NAME
|
||||
""".trimIndentMultiline()
|
||||
)
|
||||
|
||||
block()
|
||||
|
||||
dependsOn(
|
||||
addResourcesPatch,
|
||||
bytecodePatch {
|
||||
execute {
|
||||
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||
0,
|
||||
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setBranding()V"
|
||||
)
|
||||
|
||||
numberOfPresetAppNamesExtensionFingerprint.method.returnEarly(numberOfPresetAppNames)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
finalize {
|
||||
val useCustomName = customName != null
|
||||
val useCustomIcon = customIcon != null
|
||||
|
||||
if (setOrGetFallbackPackageName(originalAppPackageName) == originalAppPackageName) {
|
||||
if (useCustomName || useCustomIcon) {
|
||||
Logger.getLogger(this::class.java.name).warning(
|
||||
"Custom branding does not work with root installation. No changes applied."
|
||||
)
|
||||
}
|
||||
return@finalize
|
||||
}
|
||||
|
||||
preferenceScreen.addPreferences(
|
||||
if (useCustomName) {
|
||||
ListPreference(
|
||||
key = "revanced_custom_branding_name",
|
||||
entriesKey = "revanced_custom_branding_name_custom_entries",
|
||||
entryValuesKey = "revanced_custom_branding_name_custom_entry_values"
|
||||
)
|
||||
} else {
|
||||
ListPreference("revanced_custom_branding_name")
|
||||
},
|
||||
if (useCustomIcon) {
|
||||
ListPreference(
|
||||
key = "revanced_custom_branding_icon",
|
||||
entriesKey = "revanced_custom_branding_icon_custom_entries",
|
||||
entryValuesKey = "revanced_custom_branding_icon_custom_entry_values"
|
||||
)
|
||||
} else {
|
||||
ListPreference("revanced_custom_branding_icon")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
execute {
|
||||
val mipmapIconResourceGroups = mipmapDirectories.map { directory ->
|
||||
ResourceGroup(
|
||||
directory,
|
||||
*iconResourceFileNamesPng,
|
||||
addResources("shared", "layout.branding.baseCustomBrandingPatch")
|
||||
addResources(addResourcePatchName, "layout.branding.customBrandingPatch")
|
||||
|
||||
val useCustomName = customName != null
|
||||
val useCustomIcon = customIcon != null
|
||||
|
||||
iconStyleNames.forEach { style ->
|
||||
copyResources(
|
||||
"custom-branding",
|
||||
ResourceGroup(
|
||||
"drawable",
|
||||
"$LAUNCHER_ADAPTIVE_BACKGROUND_PREFIX$style.xml",
|
||||
"$LAUNCHER_ADAPTIVE_FOREGROUND_PREFIX$style.xml",
|
||||
"$LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX$style.xml"
|
||||
),
|
||||
ResourceGroup(
|
||||
"mipmap-anydpi",
|
||||
"$LAUNCHER_RESOURCE_NAME_PREFIX$style.xml"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val iconPathTrimmed = iconPath!!.trim()
|
||||
if (iconPathTrimmed == REVANCED_ICON) {
|
||||
// Replace mipmap icons with preset patch icons.
|
||||
mipmapIconResourceGroups.forEach { groupResources ->
|
||||
copyResources(resourceFolder, groupResources)
|
||||
}
|
||||
// Copy template user icon, because the aliases must be added even if no user icon is provided.
|
||||
copyResources(
|
||||
"custom-branding",
|
||||
ResourceGroup(
|
||||
"mipmap-anydpi",
|
||||
"$LAUNCHER_RESOURCE_NAME_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.xml",
|
||||
),
|
||||
ResourceGroup(
|
||||
"drawable",
|
||||
"$LAUNCHER_ADAPTIVE_MONOCHROME_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.xml",
|
||||
)
|
||||
)
|
||||
|
||||
// Replace monochrome icons.
|
||||
monochromeIconFileNames.forEach { fileName ->
|
||||
copyResources(
|
||||
resourceFolder,
|
||||
ResourceGroup("drawable", fileName)
|
||||
// Copy template icon png files.
|
||||
mipmapDirectories.forEach { dpi ->
|
||||
copyResources(
|
||||
"custom-branding",
|
||||
ResourceGroup(
|
||||
dpi,
|
||||
"$LAUNCHER_ADAPTIVE_BACKGROUND_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.png",
|
||||
"$LAUNCHER_ADAPTIVE_FOREGROUND_PREFIX$CUSTOM_USER_ICON_STYLE_NAME.png",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (useCustomIcon) {
|
||||
// Copy user provided files
|
||||
val iconPathFile = File(customIcon!!.trim())
|
||||
|
||||
if (!iconPathFile.exists()) {
|
||||
throw PatchException(
|
||||
"The custom icon path cannot be found: " + iconPathFile.absolutePath
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val filePath = File(iconPathTrimmed)
|
||||
|
||||
if (!iconPathFile.isDirectory) {
|
||||
throw PatchException(
|
||||
"The custom icon path must be a folder: " + iconPathFile.absolutePath
|
||||
)
|
||||
}
|
||||
|
||||
val sourceFolders = iconPathFile.listFiles { file -> file.isDirectory }
|
||||
?: throw PatchException("The custom icon path contains no subfolders: " +
|
||||
iconPathFile.absolutePath)
|
||||
|
||||
val resourceDirectory = get("res")
|
||||
var copiedFiles = false
|
||||
|
||||
// Replace
|
||||
mipmapIconResourceGroups.forEach { groupResources ->
|
||||
val groupResourceDirectoryName = groupResources.resourceDirectoryName
|
||||
val fromDirectory = filePath.resolve(groupResourceDirectoryName)
|
||||
val toDirectory = resourceDirectory.resolve(groupResourceDirectoryName)
|
||||
// For each source folder, copy the files to the target resource directories.
|
||||
sourceFolders.forEach { dpiSourceFolder ->
|
||||
val targetDpiFolder = resourceDirectory.resolve(dpiSourceFolder.name)
|
||||
if (!targetDpiFolder.exists()) return@forEach
|
||||
|
||||
groupResources.resources.forEach { iconFileName ->
|
||||
Files.write(
|
||||
toDirectory.resolve(iconFileName).toPath(),
|
||||
fromDirectory.resolve(iconFileName).readBytes(),
|
||||
)
|
||||
val customFiles = dpiSourceFolder.listFiles { file ->
|
||||
file.isFile && file.name in USER_CUSTOM_ADAPTIVE_FILE_NAMES
|
||||
}!!
|
||||
|
||||
if (customFiles.size > 0 && customFiles.size != USER_CUSTOM_ADAPTIVE_FILE_NAMES.size) {
|
||||
throw PatchException("Must include all required icon files " +
|
||||
"but only found " + customFiles.map { it.name })
|
||||
}
|
||||
|
||||
customFiles.forEach { imgSourceFile ->
|
||||
val imgTargetFile = targetDpiFolder.resolve(imgSourceFile.name)
|
||||
imgSourceFile.copyTo(target = imgTargetFile, overwrite = true)
|
||||
|
||||
copiedFiles = true
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all monochrome icons if provided.
|
||||
monochromeIconFileNames.forEach { fileName ->
|
||||
val replacementMonochrome = filePath.resolve("drawable").resolve(fileName)
|
||||
if (replacementMonochrome.exists()) {
|
||||
Files.write(
|
||||
resourceDirectory.resolve("drawable").resolve(fileName).toPath(),
|
||||
replacementMonochrome.readBytes(),
|
||||
)
|
||||
}
|
||||
// Copy monochrome if it provided.
|
||||
val monochromeRelativePath = "drawable/$USER_CUSTOM_MONOCHROME_NAME"
|
||||
val monochromeFile = iconPathFile.resolve(monochromeRelativePath)
|
||||
if (monochromeFile.exists()) {
|
||||
monochromeFile.copyTo(
|
||||
target = resourceDirectory.resolve(monochromeRelativePath),
|
||||
overwrite = true
|
||||
)
|
||||
copiedFiles = true
|
||||
}
|
||||
|
||||
if (!copiedFiles) {
|
||||
throw PatchException("Could not find any replacement images in " +
|
||||
"patch option path: " + iconPathFile.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
// Change the app name.
|
||||
escapeAppName(appName!!)?.let { escapedAppName ->
|
||||
val newValue = "android:label=\"$escapedAppName\""
|
||||
document("AndroidManifest.xml").use { document ->
|
||||
// Create launch aliases that can be programmatically selected in app.
|
||||
fun createAlias(
|
||||
aliasName: String,
|
||||
iconMipmapName: String,
|
||||
appNameIndex: Int,
|
||||
useCustomName: Boolean,
|
||||
enabled: Boolean,
|
||||
intents: NodeList
|
||||
): Element {
|
||||
val label = if (useCustomName) {
|
||||
if (customName == null) {
|
||||
"Custom" // Dummy text, and normally cannot be seen.
|
||||
} else {
|
||||
customName!!
|
||||
}
|
||||
} else if (appNameIndex == 1) {
|
||||
// Indexing starts at 1.
|
||||
originalAppName
|
||||
} else {
|
||||
"@string/revanced_custom_branding_name_entry_$appNameIndex"
|
||||
}
|
||||
val alias = document.createElement("activity-alias")
|
||||
alias.setAttribute("android:name", aliasName)
|
||||
alias.setAttribute("android:enabled", enabled.toString())
|
||||
alias.setAttribute("android:exported", "true")
|
||||
alias.setAttribute("android:icon", "@mipmap/$iconMipmapName")
|
||||
alias.setAttribute("android:label", label)
|
||||
alias.setAttribute("android:targetActivity", mainActivityName)
|
||||
|
||||
val manifest = get("AndroidManifest.xml")
|
||||
val original = manifest.readText()
|
||||
val replacement = original
|
||||
// YouTube
|
||||
.replace("android:label=\"@string/application_name\"", newValue)
|
||||
// YT Music
|
||||
.replace("android:label=\"@string/app_launcher_name\"", newValue)
|
||||
// Copy all intents from the original alias so long press actions still work.
|
||||
if (copyExistingIntentsToAliases) {
|
||||
for (i in 0 until intents.length) {
|
||||
alias.appendChild(
|
||||
intents.item(i).cloneNode(true)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val intentFilter = document.createElement("intent-filter").apply {
|
||||
val action = document.createElement("action")
|
||||
action.setAttribute("android:name", "android.intent.action.MAIN")
|
||||
appendChild(action)
|
||||
|
||||
if (original == replacement) {
|
||||
Logger.getLogger(this::class.java.name).warning(
|
||||
"Could not replace manifest app name"
|
||||
val category = document.createElement("category")
|
||||
category.setAttribute("android:name", "android.intent.category.LAUNCHER")
|
||||
appendChild(category)
|
||||
}
|
||||
alias.appendChild(intentFilter)
|
||||
}
|
||||
|
||||
return alias
|
||||
}
|
||||
|
||||
val intentFilters = document.childNodes.findElementByAttributeValueOrThrow(
|
||||
"android:name",
|
||||
activityAliasNameWithIntents
|
||||
).childNodes
|
||||
|
||||
val application = document.getElementsByTagName("application").item(0) as Element
|
||||
|
||||
for (appNameIndex in 1 .. numberOfPresetAppNames) {
|
||||
fun aliasName(name: String): String = ".revanced_" + name + '_' + appNameIndex
|
||||
|
||||
val useCustomNameLabel = (useCustomName && appNameIndex == numberOfPresetAppNames)
|
||||
|
||||
// Original icon.
|
||||
application.appendChild(
|
||||
createAlias(
|
||||
aliasName = aliasName(ORIGINAL_USER_ICON_STYLE_NAME),
|
||||
iconMipmapName = originalLauncherIconName,
|
||||
appNameIndex = appNameIndex,
|
||||
useCustomName = useCustomNameLabel,
|
||||
enabled = (appNameIndex == 1),
|
||||
intentFilters
|
||||
)
|
||||
)
|
||||
|
||||
// Bundled icons.
|
||||
iconStyleNames.forEachIndexed { index, style ->
|
||||
application.appendChild(
|
||||
createAlias(
|
||||
aliasName = aliasName(style),
|
||||
iconMipmapName = LAUNCHER_RESOURCE_NAME_PREFIX + style,
|
||||
appNameIndex = appNameIndex,
|
||||
useCustomName = useCustomNameLabel,
|
||||
enabled = false,
|
||||
intentFilters
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// User provided custom icon.
|
||||
//
|
||||
// Must add all aliases even if the user did not provide a custom icon of their own.
|
||||
// This is because if the user installs with an option, then repatches without the option,
|
||||
// the alias must still exist because if it was previously enabled and then it's removed
|
||||
// the app will become broken and cannot launch. Even if the app data is cleared
|
||||
// it still cannot be launched and the only fix is to uninstall the app.
|
||||
// To prevent this, always include all aliases and use dummy data if needed.
|
||||
application.appendChild(
|
||||
createAlias(
|
||||
aliasName = aliasName(CUSTOM_USER_ICON_STYLE_NAME),
|
||||
iconMipmapName = LAUNCHER_RESOURCE_NAME_PREFIX + CUSTOM_USER_ICON_STYLE_NAME,
|
||||
appNameIndex = appNameIndex,
|
||||
useCustomName = useCustomNameLabel,
|
||||
enabled = false,
|
||||
intentFilters
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
manifest.writeText(replacement)
|
||||
// Remove the main action from the original alias, otherwise two apps icons
|
||||
// can be shown in the launcher. Can only be done after adding the new aliases.
|
||||
intentFilters.findElementByAttributeValueOrThrow(
|
||||
"android:name",
|
||||
"android.intent.action.MAIN"
|
||||
).removeFromParent()
|
||||
}
|
||||
|
||||
executeBlock() // Must be after the main code to rename the new icons for YouTube 19.34+.
|
||||
executeBlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package app.revanced.patches.shared.layout.branding
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val numberOfPresetAppNamesExtensionFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
|
||||
returns("I")
|
||||
parameters()
|
||||
custom { method, classDef ->
|
||||
method.name == "numberOfPresetAppNames" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ internal val darkThemeBackgroundColorOption = stringOption(
|
||||
*/
|
||||
internal fun baseThemePatch(
|
||||
extensionClassDescriptor: String,
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
block: BytecodePatchBuilder.() -> Unit,
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {}
|
||||
) = bytecodePatch(
|
||||
name = "Theme",
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
package app.revanced.patches.shared.misc.gms
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patches.shared.misc.gms.EXTENSION_CLASS_DESCRIPTOR
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
const val GET_GMS_CORE_VENDOR_GROUP_ID_METHOD_NAME = "getGmsCoreVendorGroupId"
|
||||
|
||||
internal val gmsCoreSupportFingerprint = fingerprint {
|
||||
custom { _, classDef ->
|
||||
classDef.endsWith("GmsCoreSupport;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val googlePlayUtilityFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("I")
|
||||
@@ -28,3 +23,21 @@ internal val serviceCheckFingerprint = fingerprint {
|
||||
parameters("L", "I")
|
||||
strings("Google Play Services not available")
|
||||
}
|
||||
|
||||
internal val gmsCoreSupportFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
|
||||
returns("Ljava/lang/String;")
|
||||
parameters()
|
||||
custom { method, classDef ->
|
||||
method.name == "getGmsCoreVendorGroupId" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
|
||||
}
|
||||
}
|
||||
|
||||
internal val originalPackageNameExtensionFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
|
||||
returns("Ljava/lang/String;")
|
||||
parameters()
|
||||
custom { methodDef, classDef ->
|
||||
methodDef.name == "getOriginalPackageName" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package app.revanced.patches.shared.misc.gms
|
||||
|
||||
import app.revanced.patcher.Fingerprint
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.*
|
||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.Option
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.ResourcePatchBuilder
|
||||
import app.revanced.patcher.patch.ResourcePatchContext
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patches.all.misc.packagename.changePackageNamePatch
|
||||
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
@@ -12,7 +20,8 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.gms.Constants.ACTIONS
|
||||
import app.revanced.patches.shared.misc.gms.Constants.AUTHORITIES
|
||||
import app.revanced.patches.shared.misc.gms.Constants.PERMISSIONS
|
||||
import app.revanced.util.*
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.returnEarly
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
@@ -23,6 +32,8 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/GmsCoreSupport;"
|
||||
|
||||
private const val PACKAGE_NAME_REGEX_PATTERN = "^[a-z]\\w*(\\.[a-z]\\w*)+\$"
|
||||
|
||||
/**
|
||||
@@ -201,19 +212,18 @@ fun gmsCoreSupportPatch(
|
||||
googlePlayUtilityFingerprint.method.returnEarly(0)
|
||||
}
|
||||
|
||||
// Set original and patched package names for extension to use.
|
||||
originalPackageNameExtensionFingerprint.method.returnEarly(fromPackageName)
|
||||
|
||||
// Verify GmsCore is installed and whitelisted for power optimizations and background usage.
|
||||
mainActivityOnCreateFingerprint.method.apply {
|
||||
addInstructions(
|
||||
0,
|
||||
"invoke-static/range { p0 .. p0 }, Lapp/revanced/extension/shared/GmsCoreSupport;->" +
|
||||
"checkGmsCore(Landroid/app/Activity;)V",
|
||||
)
|
||||
}
|
||||
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||
0,
|
||||
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"checkGmsCore(Landroid/app/Activity;)V"
|
||||
)
|
||||
|
||||
// Change the vendor of GmsCore in the extension.
|
||||
gmsCoreSupportFingerprint.classDef.methods
|
||||
.single { it.name == GET_GMS_CORE_VENDOR_GROUP_ID_METHOD_NAME }
|
||||
.replaceInstruction(0, "const-string v0, \"$gmsCoreVendorGroupId\"")
|
||||
gmsCoreSupportFingerprint.method.returnEarly(gmsCoreVendorGroupId!!)
|
||||
|
||||
executeBlock()
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ internal fun spoofVideoStreamsPatch(
|
||||
fixMediaFetchHotConfig: BytecodePatchBuilder.() -> Boolean = { false },
|
||||
fixMediaFetchHotConfigAlternative: BytecodePatchBuilder.() -> Boolean = { false },
|
||||
fixParsePlaybackResponseFeatureFlag: BytecodePatchBuilder.() -> Boolean = { false },
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
block: BytecodePatchBuilder.() -> Unit,
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||
) = bytecodePatch(
|
||||
name = "Spoof video streams",
|
||||
|
||||
@@ -1,38 +1,28 @@
|
||||
package app.revanced.patches.youtube.layout.branding
|
||||
|
||||
import app.revanced.patches.shared.layout.branding.baseCustomBrandingPatch
|
||||
import app.revanced.patches.shared.layout.branding.mipmapDirectories
|
||||
import java.nio.file.Files
|
||||
|
||||
private const val APP_NAME = "YouTube ReVanced"
|
||||
|
||||
private val youtubeIconResourceFileNames_19_34 = mapOf(
|
||||
"adaptiveproduct_youtube_foreground_color_108" to "adaptiveproduct_youtube_2024_q4_foreground_color_108",
|
||||
"adaptiveproduct_youtube_background_color_108" to "adaptiveproduct_youtube_2024_q4_background_color_108",
|
||||
)
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.gms.Constants.YOUTUBE_MAIN_ACTIVITY_NAME
|
||||
import app.revanced.patches.youtube.misc.gms.Constants.YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
|
||||
|
||||
@Suppress("unused")
|
||||
val customBrandingPatch = baseCustomBrandingPatch(
|
||||
defaultAppName = APP_NAME,
|
||||
appNameValues = mapOf(
|
||||
"YouTube ReVanced" to APP_NAME,
|
||||
"YT ReVanced" to "YT ReVanced",
|
||||
"YT" to "YT",
|
||||
"YouTube" to "YouTube",
|
||||
),
|
||||
resourceFolder = "custom-branding/youtube",
|
||||
iconResourceFileNames = arrayOf(
|
||||
"adaptiveproduct_youtube_background_color_108",
|
||||
"adaptiveproduct_youtube_foreground_color_108",
|
||||
"ic_launcher",
|
||||
"ic_launcher_round",
|
||||
),
|
||||
monochromeIconFileNames = arrayOf(
|
||||
"adaptive_monochrome_ic_youtube_launcher.xml",
|
||||
"ringo2_adaptive_monochrome_ic_youtube_launcher.xml"
|
||||
),
|
||||
addResourcePatchName = "youtube",
|
||||
originalLauncherIconName = "ic_launcher",
|
||||
originalAppName = "@string/application_name",
|
||||
originalAppPackageName = YOUTUBE_PACKAGE_NAME,
|
||||
copyExistingIntentsToAliases = true,
|
||||
numberOfPresetAppNames = 5,
|
||||
mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint,
|
||||
mainActivityName = YOUTUBE_MAIN_ACTIVITY_NAME,
|
||||
activityAliasNameWithIntents = "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity",
|
||||
preferenceScreen = PreferenceScreen.GENERAL_LAYOUT,
|
||||
|
||||
block = {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
@@ -41,20 +31,5 @@ val customBrandingPatch = baseCustomBrandingPatch(
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
executeBlock = {
|
||||
val resourceDirectory = get("res")
|
||||
|
||||
mipmapDirectories.forEach { directory ->
|
||||
val targetDirectory = resourceDirectory.resolve(directory)
|
||||
|
||||
youtubeIconResourceFileNames_19_34.forEach { (old, new) ->
|
||||
val oldFile = targetDirectory.resolve("$old.png")
|
||||
val newFile = targetDirectory.resolve("$new.png")
|
||||
|
||||
Files.write(newFile.toPath(), oldFile.readBytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -136,8 +136,21 @@ val changeHeaderPatch = resourcePatch(
|
||||
)
|
||||
|
||||
if (custom != null) {
|
||||
val sourceFolders = File(custom!!).listFiles { file -> file.isDirectory }
|
||||
?: throw PatchException("The provided path is not a directory: $custom")
|
||||
val customFile = File(custom!!)
|
||||
if (!customFile.exists()) {
|
||||
throw PatchException("The custom icon path cannot be found: " +
|
||||
customFile.absolutePath
|
||||
)
|
||||
}
|
||||
|
||||
if (!customFile.isDirectory) {
|
||||
throw PatchException("The custom icon path must be a folder: "
|
||||
+ customFile.absolutePath)
|
||||
}
|
||||
|
||||
val sourceFolders = customFile.listFiles { file -> file.isDirectory }
|
||||
?: throw PatchException("The custom icon path contains no subfolders: " +
|
||||
customFile.absolutePath)
|
||||
|
||||
val customResourceFileNames = getLightDarkFileNames(CUSTOM_HEADER_RESOURCE_NAME)
|
||||
|
||||
@@ -166,7 +179,8 @@ val changeHeaderPatch = resourcePatch(
|
||||
}
|
||||
|
||||
if (!copiedFiles) {
|
||||
throw PatchException("No custom header images found in the provided path: $custom")
|
||||
throw PatchException("No custom header images found in " +
|
||||
"the provided path: " + customFile.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import app.revanced.util.forEachLiteralValueInstruction
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstruction
|
||||
import app.revanced.util.removeFromParent
|
||||
import app.revanced.util.returnLate
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
@@ -127,7 +128,7 @@ private val hideShortsComponentsResourcePatch = resourcePatch {
|
||||
)
|
||||
|
||||
if (hideShortsAppShortcut == true) {
|
||||
shortsItem.parentNode.removeChild(shortsItem)
|
||||
shortsItem.removeFromParent()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +139,7 @@ private val hideShortsComponentsResourcePatch = resourcePatch {
|
||||
)
|
||||
|
||||
if (hideShortsWidget == true) {
|
||||
shortsItem.parentNode.removeChild(shortsItem)
|
||||
shortsItem.removeFromParent()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package app.revanced.patches.youtube.misc.gms
|
||||
|
||||
internal object Constants {
|
||||
internal const val YOUTUBE_MAIN_ACTIVITY_NAME = "com.google.android.apps.youtube.app.watchwhile.MainActivity"
|
||||
|
||||
const val YOUTUBE_PACKAGE_NAME = "com.google.android.youtube"
|
||||
const val REVANCED_YOUTUBE_PACKAGE_NAME = "app.revanced.android.youtube"
|
||||
}
|
||||
@@ -14,6 +14,13 @@ import java.nio.file.StandardCopyOption
|
||||
|
||||
private val classLoader = object {}.javaClass.classLoader
|
||||
|
||||
/**
|
||||
* Removes a node from its parent.
|
||||
*
|
||||
* @return The node that was removed (object this method was called on).
|
||||
*/
|
||||
fun Node.removeFromParent() : Node = parentNode.removeChild(this)
|
||||
|
||||
/**
|
||||
* Returns a sequence for all child nodes.
|
||||
*/
|
||||
@@ -70,8 +77,13 @@ fun ResourcePatchContext.copyResources(
|
||||
for (resourceGroup in resources) {
|
||||
resourceGroup.resources.forEach { resource ->
|
||||
val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource"
|
||||
val stream = inputStreamFromBundledResource(sourceResourceDirectory, resourceFile)
|
||||
if (stream == null) {
|
||||
throw IllegalArgumentException("Could not find resource: $resourceFile " +
|
||||
"in directory: $sourceResourceDirectory")
|
||||
}
|
||||
Files.copy(
|
||||
inputStreamFromBundledResource(sourceResourceDirectory, resourceFile)!!,
|
||||
stream,
|
||||
targetResourceDirectory.resolve(resourceFile).toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
<resources>
|
||||
<app id="shared">
|
||||
<patch id="layout.branding.baseCustomBrandingPatch">
|
||||
<string-array name="revanced_custom_branding_icon_entries">
|
||||
<item>@string/revanced_custom_branding_icon_entry_1</item>
|
||||
<item>@string/revanced_custom_branding_icon_entry_2</item>
|
||||
<item>@string/revanced_custom_branding_icon_entry_3</item>
|
||||
<item>@string/revanced_custom_branding_icon_entry_4</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_custom_branding_icon_entry_values">
|
||||
<item>ORIGINAL</item>
|
||||
<item>ROUNDED</item>
|
||||
<item>MINIMAL</item>
|
||||
<item>SCALED</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_custom_branding_icon_custom_entries">
|
||||
<item>@string/revanced_custom_branding_icon_entry_1</item>
|
||||
<item>@string/revanced_custom_branding_icon_entry_2</item>
|
||||
<item>@string/revanced_custom_branding_icon_entry_3</item>
|
||||
<item>@string/revanced_custom_branding_icon_entry_4</item>
|
||||
<item>@string/revanced_custom_branding_icon_entry_5</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_custom_branding_icon_custom_entry_values">
|
||||
<item>ORIGINAL</item>
|
||||
<item>ROUNDED</item>
|
||||
<item>MINIMAL</item>
|
||||
<item>SCALED</item>
|
||||
<item>CUSTOM</item>
|
||||
</string-array>
|
||||
</patch>
|
||||
<patch id="misc.settings.settingsResourcePatch">
|
||||
<string-array name="revanced_language_entries">
|
||||
<item>@string/revanced_language_DEFAULT</item>
|
||||
@@ -123,6 +151,36 @@
|
||||
</patch>
|
||||
</app>
|
||||
<app id="music">
|
||||
<patch id="layout.branding.customBrandingPatch">
|
||||
<string-array name="revanced_custom_branding_name_entries">
|
||||
<!-- Original must be first. -->
|
||||
<item>@string/app_launcher_name</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_2</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_3</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_4</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_custom_branding_name_entry_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_custom_branding_name_custom_entries">
|
||||
<!-- Original must be first. -->
|
||||
<item>@string/app_launcher_name</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_2</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_3</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_4</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_5</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_custom_branding_name_custom_entry_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
</string-array>
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
|
||||
<string-array name="revanced_spoof_video_streams_client_type_entries">
|
||||
<item>Android VR</item>
|
||||
@@ -135,6 +193,36 @@
|
||||
</patch>
|
||||
</app>
|
||||
<app id="youtube">
|
||||
<patch id="layout.branding.customBrandingPatch">
|
||||
<string-array name="revanced_custom_branding_name_entries">
|
||||
<!-- Original must be first. -->
|
||||
<item>@string/application_name</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_2</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_3</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_4</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_custom_branding_name_entry_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_custom_branding_name_custom_entries">
|
||||
<!-- Original must be first. -->
|
||||
<item>@string/application_name</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_2</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_3</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_4</item>
|
||||
<item>@string/revanced_custom_branding_name_entry_5</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_custom_branding_name_custom_entry_values">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
</string-array>
|
||||
</patch>
|
||||
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
|
||||
<string-array name="revanced_spoof_video_streams_client_type_entries">
|
||||
<item>Android VR</item>
|
||||
|
||||
@@ -19,6 +19,19 @@ Second \"item\" text"</string>
|
||||
-->
|
||||
<resources>
|
||||
<app id="shared">
|
||||
<patch id="layout.branding.baseCustomBrandingPatch">
|
||||
<string name="revanced_custom_branding_name_title">App name</string>
|
||||
<!-- Translations of this should be identical to revanced_custom_branding_icon_entry_5 -->
|
||||
<string name="revanced_custom_branding_name_entry_5">Custom</string>
|
||||
<string name="revanced_custom_branding_icon_title">App icon</string>
|
||||
<string name="revanced_custom_branding_icon_entry_1">Original</string>
|
||||
<string name="revanced_custom_branding_icon_entry_2">ReVanced</string>
|
||||
<!-- Translation of this should be identical to revanced_header_logo_entry_5 -->
|
||||
<string name="revanced_custom_branding_icon_entry_3">ReVanced minimal</string>
|
||||
<string name="revanced_custom_branding_icon_entry_4">ReVanced scaled</string>
|
||||
<!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 -->
|
||||
<string name="revanced_custom_branding_icon_entry_5">Custom</string>
|
||||
</patch>
|
||||
<patch id="misc.checks.checkEnvironmentPatch">
|
||||
<string name="revanced_check_environment_failed_title">Checks failed</string>
|
||||
<string name="revanced_check_environment_dialog_open_official_source_button">Open official website</string>
|
||||
@@ -1484,13 +1497,18 @@ Swipe to expand or close"</string>
|
||||
<string name="revanced_seekbar_custom_color_accent_summary">The accent color of the seekbar</string>
|
||||
<string name="revanced_seekbar_custom_color_invalid">Invalid seekbar color value</string>
|
||||
</patch>
|
||||
<patch id="layout.branding.customBrandingPatch">
|
||||
<string name="revanced_custom_branding_name_entry_2" translatable="false">YouTube ReVanced</string>
|
||||
<string name="revanced_custom_branding_name_entry_3" translatable="false">YT ReVanced</string>
|
||||
<string name="revanced_custom_branding_name_entry_4" translatable="false">YT</string>
|
||||
</patch>
|
||||
<patch id="layout.branding.changeHeaderPatch">
|
||||
<string name="revanced_header_logo_title">Header logo</string>
|
||||
<string name="revanced_header_logo_entry_1">Default</string>
|
||||
<string name="revanced_header_logo_entry_2">Regular</string>
|
||||
<string name="revanced_header_logo_entry_3" translatable="false">Premium</string>
|
||||
<string name="revanced_header_logo_entry_4" translatable="false">ReVanced</string>
|
||||
<!-- For this situation "Minimal" means minimalistic. It does not mean small or tiny. -->
|
||||
<!-- Translation of this should be identical to revanced_custom_branding_icon_entry_3 -->
|
||||
<string name="revanced_header_logo_entry_5">ReVanced minimal</string>
|
||||
<string name="revanced_header_logo_entry_6">Custom</string>
|
||||
</patch>
|
||||
@@ -1708,6 +1726,11 @@ Video playback with AV1 may stutter or drop frames."</string>
|
||||
</patch>
|
||||
</app>
|
||||
<app id="music">
|
||||
<patch id="layout.branding.customBrandingPatch">
|
||||
<string name="revanced_custom_branding_name_entry_2" translatable="false">YT Music ReVanced</string>
|
||||
<string name="revanced_custom_branding_name_entry_3" translatable="false">Music ReVanced</string>
|
||||
<string name="revanced_custom_branding_name_entry_4" translatable="false">Music</string>
|
||||
</patch>
|
||||
<patch id="misc.settings.settingsPatch">
|
||||
<string name="revanced_settings_music_screen_0_about_title">About</string>
|
||||
<string name="revanced_settings_music_screen_1_ads_title">Ads</string>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,2 @@
|
||||
<color xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#1B1B1B" />
|
||||
@@ -0,0 +1,2 @@
|
||||
<color xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#1B1B1B" />
|
||||
@@ -0,0 +1,2 @@
|
||||
<color xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#000000" />
|
||||
@@ -0,0 +1,36 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<!-- Android launcher can hide the background layer if it's black or nearly black.
|
||||
The dark background is needed to see the V shape in day mode. To prevent hiding
|
||||
the layer it must be added to the foreground. -->
|
||||
<path
|
||||
android:pathData="M0,0 L256,0 L256,256 L0,256 Z"
|
||||
android:fillColor="#1B1B1B" />
|
||||
<group android:scaleX="0.24"
|
||||
android:scaleY="0.24"
|
||||
android:translateX="97.28"
|
||||
android:translateY="97.28">
|
||||
<path
|
||||
android:pathData="M250.09,13.49C251.39,10.51 251.11,7.08 249.33,4.36C247.55,1.64 244.52,0 241.27,0L228.81,0C226.08,0 223.61,1.62 222.51,4.11C211.54,29.1 153.63,160.99 134.29,205.04C133.2,207.54 130.73,209.15 128,209.15C125.27,209.15 122.8,207.54 121.7,205.04C102.36,160.99 44.46,29.1 33.49,4.11C32.39,1.62 29.92,0 27.19,0L14.73,0C11.48,0 8.45,1.64 6.67,4.36C4.89,7.08 4.61,10.51 5.91,13.49C26.64,60.8 95.56,218.1 109.63,250.24C111.17,253.74 114.63,256 118.45,256L137.55,256C141.37,256 144.83,253.74 146.36,250.24C160.44,218.1 229.36,60.8 250.09,13.49Z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M135.14,123.87C133.67,126.43 130.94,128 128,128C125.05,128 122.33,126.43 120.85,123.87C105.89,97.97 71.44,38.28 56.48,12.37C55,9.82 55,6.68 56.48,4.12C57.95,1.57 60.68,-0 63.62,-0L192.37,-0C195.32,-0 198.04,1.57 199.52,4.12C200.99,6.68 200.99,9.82 199.52,12.37C184.56,38.28 150.1,97.97 135.14,123.87Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="128"
|
||||
android:startY="-0"
|
||||
android:endX="128"
|
||||
android:endY="254.6"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFF04E98"/>
|
||||
<item android:offset="0.5" android:color="#FF5F65D4"/>
|
||||
<item android:offset="1" android:color="#FF4E98F0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,50 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="800"
|
||||
android:viewportHeight="800">
|
||||
<group android:scaleX="0.5"
|
||||
android:scaleY="0.5"
|
||||
android:translateX="200"
|
||||
android:translateY="200">
|
||||
<path
|
||||
android:pathData="M400,400m-400,0a400,400 0,1 1,800 0a400,400 0,1 1,-800 0"
|
||||
android:fillColor="#1B1B1B"/>
|
||||
<path
|
||||
android:pathData="M400,0c220.77,0 400,179.23 400,400c-0,220.77 -179.23,400 -400,400c-220.77,-0 -400,-179.23 -400,-400c0,-220.77 179.23,-400 400,-400ZM400,36c200.9,-0 364,163.1 364,364c0,200.9 -163.1,364 -364,364c-200.9,0 -364,-163.1 -364,-364c-0,-200.9 163.1,-364 364,-364Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillType="evenOdd">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="400"
|
||||
android:startY="0"
|
||||
android:endX="400"
|
||||
android:endY="800"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFF04E98"/>
|
||||
<item android:offset="0.5" android:color="#FF5F65D4"/>
|
||||
<item android:offset="1" android:color="#FF4E98F0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M538.74,269.87c1.48,-3.38 1.16,-7.28 -0.86,-10.37c-2.02,-3.09 -5.46,-4.95 -9.16,-4.95c-5.15,0 -10.44,0 -14.16,0c-3.1,0 -5.91,1.83 -7.15,4.67c-12.47,28.4 -78.27,178.27 -100.25,228.33c-1.25,2.84 -4.05,4.67 -7.15,4.67c-3.1,0 -5.91,-1.83 -7.15,-4.67c-21.98,-50.06 -87.78,-199.93 -100.25,-228.33c-1.25,-2.84 -4.05,-4.67 -7.15,-4.67c-3.73,0 -9.02,0 -14.16,0c-3.69,0 -7.14,1.86 -9.16,4.95c-2.02,3.09 -2.34,6.99 -0.86,10.37c23.56,53.77 101.87,232.52 117.87,269.03c1.74,3.98 5.67,6.55 10.02,6.55c6.29,-0 15.41,-0 21.7,-0c4.34,-0 8.27,-2.57 10.02,-6.55c16,-36.51 94.32,-215.27 117.87,-269.03Z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M408.12,395.31c-1.67,2.9 -4.77,4.69 -8.12,4.69c-3.35,-0 -6.44,-1.79 -8.12,-4.69c-17,-29.44 -56.16,-97.26 -73.15,-126.7c-1.67,-2.9 -1.67,-6.47 0,-9.38c1.67,-2.9 4.77,-4.69 8.12,-4.69c33.99,0 112.31,0 146.31,0c3.35,0 6.44,1.79 8.12,4.69c1.67,2.9 1.67,6.47 -0,9.38c-17,29.44 -56.16,97.26 -73.15,126.7Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="400"
|
||||
android:startY="254.54"
|
||||
android:endX="400"
|
||||
android:endY="543.86"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFF04E98"/>
|
||||
<item android:offset="0.5" android:color="#FF5F65D4"/>
|
||||
<item android:offset="1" android:color="#FF4E98F0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,36 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<!-- Android launcher can hide the background layer if it's black or nearly black.
|
||||
The dark background is needed to see the V shape in day mode. To prevent hiding
|
||||
the layer it must be added to the foreground. -->
|
||||
<path
|
||||
android:pathData="M0,0 L256,0 L256,256 L0,256 Z"
|
||||
android:fillColor="#000000" />
|
||||
<group android:scaleX="0.3"
|
||||
android:scaleY="0.3"
|
||||
android:translateX="89.6"
|
||||
android:translateY="89.6">
|
||||
<path
|
||||
android:pathData="M250.09,13.49C251.39,10.51 251.11,7.08 249.33,4.36C247.55,1.64 244.52,0 241.27,0L228.81,0C226.08,0 223.61,1.62 222.51,4.11C211.54,29.1 153.63,160.99 134.29,205.04C133.2,207.54 130.73,209.15 128,209.15C125.27,209.15 122.8,207.54 121.7,205.04C102.36,160.99 44.46,29.1 33.49,4.11C32.39,1.62 29.92,0 27.19,0L14.73,0C11.48,0 8.45,1.64 6.67,4.36C4.89,7.08 4.61,10.51 5.91,13.49C26.64,60.8 95.56,218.1 109.63,250.24C111.17,253.74 114.63,256 118.45,256L137.55,256C141.37,256 144.83,253.74 146.36,250.24C160.44,218.1 229.36,60.8 250.09,13.49Z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M135.14,123.87C133.67,126.43 130.94,128 128,128C125.05,128 122.33,126.43 120.85,123.87C105.89,97.97 71.44,38.28 56.48,12.37C55,9.82 55,6.68 56.48,4.12C57.95,1.57 60.68,-0 63.62,-0L192.37,-0C195.32,-0 198.04,1.57 199.52,4.12C200.99,6.68 200.99,9.82 199.52,12.37C184.56,38.28 150.1,97.97 135.14,123.87Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="128"
|
||||
android:startY="0"
|
||||
android:endX="128"
|
||||
android:endY="254.6"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFF04E98"/>
|
||||
<item android:offset="0.5" android:color="#FF5F65D4"/>
|
||||
<item android:offset="1" android:color="#FF4E98F0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -3,10 +3,10 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<group android:scaleX="0.3"
|
||||
android:scaleY="0.3"
|
||||
android:translateX="89.6"
|
||||
android:translateY="89.6">
|
||||
<group android:scaleX="0.24"
|
||||
android:scaleY="0.24"
|
||||
android:translateX="97.28"
|
||||
android:translateY="97.28">
|
||||
<path
|
||||
android:fillColor="#ff000000"
|
||||
android:pathData="M250.09,13.49C251.39,10.51 251.11,7.08 249.33,4.36C247.55,1.64 244.52,0 241.27,0L228.81,0C226.08,0 223.61,1.62 222.51,4.11C211.54,29.1 153.63,160.99 134.29,205.04C133.2,207.54 130.73,209.15 128,209.15C125.27,209.15 122.8,207.54 121.7,205.04C102.36,160.99 44.46,29.1 33.49,4.11C32.39,1.62 29.92,0 27.19,0L14.73,0C11.48,0 8.45,1.64 6.67,4.36C4.89,7.08 4.61,10.51 5.91,13.49C26.64,60.8 95.56,218.1 109.63,250.24C111.17,253.74 114.63,256 118.45,256L137.55,256C141.37,256 144.83,253.74 146.36,250.24C160.44,218.1 229.36,60.8 250.09,13.49Z"/>
|
||||
@@ -3,10 +3,10 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<group android:scaleX="0.3"
|
||||
android:scaleY="0.3"
|
||||
android:translateX="89.6"
|
||||
android:translateY="89.6">
|
||||
<group android:scaleX="0.24"
|
||||
android:scaleY="0.24"
|
||||
android:translateX="97.28"
|
||||
android:translateY="97.28">
|
||||
<path
|
||||
android:fillColor="#ff000000"
|
||||
android:pathData="M250.09,13.49C251.39,10.51 251.11,7.08 249.33,4.36C247.55,1.64 244.52,0 241.27,0L228.81,0C226.08,0 223.61,1.62 222.51,4.11C211.54,29.1 153.63,160.99 134.29,205.04C133.2,207.54 130.73,209.15 128,209.15C125.27,209.15 122.8,207.54 121.7,205.04C102.36,160.99 44.46,29.1 33.49,4.11C32.39,1.62 29.92,0 27.19,0L14.73,0C11.48,0 8.45,1.64 6.67,4.36C4.89,7.08 4.61,10.51 5.91,13.49C26.64,60.8 95.56,218.1 109.63,250.24C111.17,253.74 114.63,256 118.45,256L137.55,256C141.37,256 144.83,253.74 146.36,250.24C160.44,218.1 229.36,60.8 250.09,13.49Z"/>
|
||||
@@ -0,0 +1,22 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="800"
|
||||
android:viewportHeight="800">
|
||||
<group android:scaleX="0.5"
|
||||
android:scaleY="0.5"
|
||||
android:translateX="200"
|
||||
android:translateY="200">
|
||||
<path
|
||||
android:fillColor="#ff000000"
|
||||
android:pathData="M400,0c220.77,0 400,179.23 400,400c-0,220.77 -179.23,400 -400,400c-220.77,-0 -400,-179.23 -400,-400c0,-220.77 179.23,-400 400,-400ZM400,36c200.9,-0 364,163.1 364,364c0,200.9 -163.1,364 -364,364c-200.9,0 -364,-163.1 -364,-364c-0,-200.9 163.1,-364 364,-364Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:fillColor="#ff000000"
|
||||
android:pathData="M538.74,269.87c1.48,-3.38 1.16,-7.28 -0.86,-10.37c-2.02,-3.09 -5.46,-4.95 -9.16,-4.95c-5.15,0 -10.44,0 -14.16,0c-3.1,0 -5.91,1.83 -7.15,4.67c-12.47,28.4 -78.27,178.27 -100.25,228.33c-1.25,2.84 -4.05,4.67 -7.15,4.67c-3.1,0 -5.91,-1.83 -7.15,-4.67c-21.98,-50.06 -87.78,-199.93 -100.25,-228.33c-1.25,-2.84 -4.05,-4.67 -7.15,-4.67c-3.73,0 -9.02,0 -14.16,0c-3.69,0 -7.14,1.86 -9.16,4.95c-2.02,3.09 -2.34,6.99 -0.86,10.37c23.56,53.77 101.87,232.52 117.87,269.03c1.74,3.98 5.67,6.55 10.02,6.55c6.29,-0 15.41,-0 21.7,-0c4.34,-0 8.27,-2.57 10.02,-6.55c16,-36.51 94.32,-215.27 117.87,-269.03Z"/>
|
||||
<path
|
||||
android:fillColor="#ff000000"
|
||||
android:pathData="M408.12,395.31c-1.67,2.9 -4.77,4.69 -8.12,4.69c-3.35,-0 -6.44,-1.79 -8.12,-4.69c-17,-29.44 -56.16,-97.26 -73.15,-126.7c-1.67,-2.9 -1.67,-6.47 0,-9.38c1.67,-2.9 4.77,-4.69 8.12,-4.69c33.99,0 112.31,0 146.31,0c3.35,0 6.44,1.79 8.12,4.69c1.67,2.9 1.67,6.47 -0,9.38c-17,29.44 -56.16,97.26 -73.15,126.7Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/revanced_adaptive_background_custom" />
|
||||
<foreground android:drawable="@mipmap/revanced_adaptive_foreground_custom" />
|
||||
<monochrome android:drawable="@drawable/revanced_adaptive_monochrome_custom" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/revanced_adaptive_background_minimal" />
|
||||
<foreground android:drawable="@drawable/revanced_adaptive_foreground_minimal" />
|
||||
<monochrome android:drawable="@drawable/revanced_adaptive_monochrome_minimal" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/revanced_adaptive_background_rounded" />
|
||||
<foreground android:drawable="@drawable/revanced_adaptive_foreground_rounded" />
|
||||
<monochrome android:drawable="@drawable/revanced_adaptive_monochrome_rounded" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/revanced_adaptive_background_scaled" />
|
||||
<foreground android:drawable="@drawable/revanced_adaptive_foreground_scaled" />
|
||||
<monochrome android:drawable="@drawable/revanced_adaptive_monochrome_scaled" />
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 94 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 92 B |
|
After Width: | Height: | Size: 858 B |
|
After Width: | Height: | Size: 98 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 108 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 118 B |
|
After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 98 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 92 B |
|
Before Width: | Height: | Size: 943 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 100 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 108 B |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 118 B |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 98 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 92 B |
|
Before Width: | Height: | Size: 943 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 100 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 108 B |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 118 B |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |