Compare commits

...

20 Commits

Author SHA1 Message Date
semantic-release-bot
b2fcd5a846 chore: Release v5.24.0-dev.5 [skip ci]
# [5.24.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.4...v5.24.0-dev.5) (2025-05-17)

### Bug Fixes

* **Spotify - Fix third party launchers widgets:** Add missing compatibility annotation ([e68cd70](e68cd70f66))

### Features

* **YouTube - Settings:** Add ability to search in settings ([#4881](https://github.com/ReVanced/revanced-patches/issues/4881)) ([14a8f4f](14a8f4fb96))
2025-05-17 07:44:42 +00:00
LisoUseInAIKyrios
e68cd70f66 fix(Spotify - Fix third party launchers widgets): Add missing compatibility annotation 2025-05-17 11:40:37 +04:00
MarcaD
14a8f4fb96 feat(YouTube - Settings): Add ability to search in settings (#4881)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-17 11:40:16 +04:00
semantic-release-bot
2593c004f4 chore: Release v5.24.0-dev.4 [skip ci]
# [5.24.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.3...v5.24.0-dev.4) (2025-05-16)

### Features

* **Spotify:** Add `Fix third party launchers widgets` patch ([#4893](https://github.com/ReVanced/revanced-patches/issues/4893)) ([db68c41](db68c41d5e))
2025-05-16 17:18:15 +00:00
Nuckyz
db68c41d5e feat(Spotify): Add Fix third party launchers widgets patch (#4893)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-16 19:14:26 +02:00
semantic-release-bot
a4f9cb3cef chore: Release v5.24.0-dev.3 [skip ci]
# [5.24.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.2...v5.24.0-dev.3) (2025-05-14)

### Bug Fixes

* **YouTube - Hide layout components:** Fix `Hide video recommendation labels` ([#4956](https://github.com/ReVanced/revanced-patches/issues/4956)) ([9aec199](9aec1999bb))
2025-05-14 20:01:05 +00:00
ILoveOpenSourceApplications
9aec1999bb fix(YouTube - Hide layout components): Fix Hide video recommendation labels (#4956) 2025-05-14 23:57:47 +04:00
github-actions[bot]
26ecbe646e chore: Sync translations (#4960) 2025-05-14 23:57:11 +04:00
semantic-release-bot
46ba0d8a2e chore: Release v5.24.0-dev.2 [skip ci]
# [5.24.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.1...v5.24.0-dev.2) (2025-05-14)

### Features

* **GmsCore support:** Open vendor specific DontKillMyApp if available ([#4952](https://github.com/ReVanced/revanced-patches/issues/4952)) ([d2b440d](d2b440d800))
* **YouTube - Hide player components:** Hide related video overlay in fullscreen ([#4938](https://github.com/ReVanced/revanced-patches/issues/4938)) ([f454183](f454183646))
2025-05-14 07:07:03 +00:00
MarcaD
f454183646 feat(YouTube - Hide player components): Hide related video overlay in fullscreen (#4938) 2025-05-14 11:03:33 +04:00
LisoUseInAIKyrios
d2b440d800 feat(GmsCore support): Open vendor specific DontKillMyApp if available (#4952) 2025-05-14 11:01:32 +04:00
hoodles
494c5f04a4 fix(Instagram) - Fix hide ads fingerprint and add bypass check signature (#4901)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-13 10:58:07 +04:00
semantic-release-bot
48d5fdf7e1 chore: Release v5.24.0-dev.1 [skip ci]
# [5.24.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.23.0...v5.24.0-dev.1) (2025-05-12)

### Features

* **NU.nl:** Support version `11.3.0` ([#4925](https://github.com/ReVanced/revanced-patches/issues/4925)) ([887c9f0](887c9f0d75))
2025-05-12 22:40:55 +00:00
Jasper Abbink
887c9f0d75 feat(NU.nl): Support version 11.3.0 (#4925) 2025-05-13 02:37:41 +04:00
github-actions[bot]
7de4c9d41d chore: Sync translations (#4946) 2025-05-13 02:36:53 +04:00
semantic-release-bot
7d3b8d9c42 chore: Release v5.23.0 [skip ci]
# [5.23.0](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0) (2025-05-10)

### Bug Fixes

* Correct incorrect fingerprint ([5f05414](5f0541407c))
* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([796c118](796c118fe1))
* **Spotify - Unlock Spotify Premium:** Remove pop up premium ads ([#4842](https://github.com/ReVanced/revanced-patches/issues/4842)) ([5028c1a](5028c1acb3))
* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([60fdf4c](60fdf4c44c))
* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([23fd720](23fd720fa7))

### Features

* **Lightroom:** Constrain patches to last working version ([858c59d](858c59d728))
* **Pandora:** Add `Disable audio ads` and `Unlimited skips` patch ([#4841](https://github.com/ReVanced/revanced-patches/issues/4841)) ([f4f36ff](f4f36ff273))
* **Prime Video:** Add `Skip ads` patch ([#4824](https://github.com/ReVanced/revanced-patches/issues/4824)) ([f8bdf74](f8bdf744ab))
* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([777957e](777957e2d0))
2025-05-10 09:02:41 +00:00
LisoUseInAIKyrios
25e1a965d6 chore: Merge branch dev to main (#4899) 2025-05-10 12:58:55 +04:00
github-actions[bot]
b29c01cee1 chore: Sync translations (#4933) 2025-05-10 12:39:30 +04:00
semantic-release-bot
639850471b chore: Release v5.23.0-dev.7 [skip ci]
# [5.23.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.6...v5.23.0-dev.7) (2025-05-06)

### Bug Fixes

* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([796c118](796c118fe1))
2025-05-06 12:13:29 +00:00
Nuckyz
796c118fe1 fix: Fix incorrect fingerprints (#4917) 2025-05-06 16:09:54 +04:00
140 changed files with 3073 additions and 1325 deletions

View File

@@ -1,3 +1,70 @@
# [5.24.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.4...v5.24.0-dev.5) (2025-05-17)
### Bug Fixes
* **Spotify - Fix third party launchers widgets:** Add missing compatibility annotation ([0493f80](https://github.com/ReVanced/revanced-patches/commit/0493f8035b26b90c5f8e42be2e2a5ce73d8685a5))
### Features
* **YouTube - Settings:** Add ability to search in settings ([#4881](https://github.com/ReVanced/revanced-patches/issues/4881)) ([aca8b20](https://github.com/ReVanced/revanced-patches/commit/aca8b207c15f254bcc9ad94bc7dfb895f21d4058))
# [5.24.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.3...v5.24.0-dev.4) (2025-05-16)
### Features
* **Spotify:** Add `Fix third party launchers widgets` patch ([#4893](https://github.com/ReVanced/revanced-patches/issues/4893)) ([23bfdc9](https://github.com/ReVanced/revanced-patches/commit/23bfdc98fbbcc8ecf0ffbf8704f58dd2272e4af2))
# [5.24.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.2...v5.24.0-dev.3) (2025-05-14)
### Bug Fixes
* **YouTube - Hide layout components:** Fix `Hide video recommendation labels` ([#4956](https://github.com/ReVanced/revanced-patches/issues/4956)) ([ae05ac3](https://github.com/ReVanced/revanced-patches/commit/ae05ac38151ebd3197953af97ca0dd847a04cc2d))
# [5.24.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.1...v5.24.0-dev.2) (2025-05-14)
### Features
* **GmsCore support:** Open vendor specific DontKillMyApp if available ([#4952](https://github.com/ReVanced/revanced-patches/issues/4952)) ([b89927a](https://github.com/ReVanced/revanced-patches/commit/b89927a10e3b909a3c37fbb75c16a7abbce44560))
* **YouTube - Hide player components:** Hide related video overlay in fullscreen ([#4938](https://github.com/ReVanced/revanced-patches/issues/4938)) ([ac9be97](https://github.com/ReVanced/revanced-patches/commit/ac9be9760c9965e54df196b227a310d64ead4bf5))
# [5.24.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.23.0...v5.24.0-dev.1) (2025-05-12)
### Features
* **NU.nl:** Support version `11.3.0` ([#4925](https://github.com/ReVanced/revanced-patches/issues/4925)) ([bedde60](https://github.com/ReVanced/revanced-patches/commit/bedde60fc1a52b0fd491174b3b5b887435eb621a))
# [5.23.0](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0) (2025-05-10)
### Bug Fixes
* Correct incorrect fingerprint ([c3bab89](https://github.com/ReVanced/revanced-patches/commit/c3bab89fc4189e38c10eee0caa36289de7e29dfa))
* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([49ca329](https://github.com/ReVanced/revanced-patches/commit/49ca3290a726cdba7bc9b62ffcd8d46e6f04778e))
* **Spotify - Unlock Spotify Premium:** Remove pop up premium ads ([#4842](https://github.com/ReVanced/revanced-patches/issues/4842)) ([00aa200](https://github.com/ReVanced/revanced-patches/commit/00aa2000ba2eef15a0dd827c2bd84c2e85c412e0))
* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([7b43986](https://github.com/ReVanced/revanced-patches/commit/7b43986871a68e5cb43331d2fb2fdb9ef67438ad))
* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([bd53955](https://github.com/ReVanced/revanced-patches/commit/bd53955df738bb7b819eb91a3e776e9d2ca5c74a))
### Features
* **Lightroom:** Constrain patches to last working version ([efef03b](https://github.com/ReVanced/revanced-patches/commit/efef03b80da21552d0d8be6913faba64e4fb5ed1))
* **Pandora:** Add `Disable audio ads` and `Unlimited skips` patch ([#4841](https://github.com/ReVanced/revanced-patches/issues/4841)) ([0cf7a4c](https://github.com/ReVanced/revanced-patches/commit/0cf7a4c6be615ed0a52a6bacf87592f5f43ff575))
* **Prime Video:** Add `Skip ads` patch ([#4824](https://github.com/ReVanced/revanced-patches/issues/4824)) ([bb672c4](https://github.com/ReVanced/revanced-patches/commit/bb672c4674ddc201b8b2648c3906cfc31ef43f10))
* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([2e3511d](https://github.com/ReVanced/revanced-patches/commit/2e3511d03c8198bbdb9336888df038a33fb3ab8c))
# [5.23.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.6...v5.23.0-dev.7) (2025-05-06)
### Bug Fixes
* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([49ca329](https://github.com/ReVanced/revanced-patches/commit/49ca3290a726cdba7bc9b62ffcd8d46e6f04778e))
# [5.23.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.5...v5.23.0-dev.6) (2025-05-06)

View File

@@ -82,7 +82,7 @@ public class HideAdsPatch {
// Filter HeaderBlock with known ads until next HeaderBlock.
if (currentBlock instanceof HeaderBlock headerBlock) {
StyledText headerText = headerBlock.component20();
StyledText headerText = headerBlock.getTitle();
if (headerText != null) {
skipFullHeader = false;
for (String blockedHeaderBlock : blockedHeaderBlocks) {

View File

@@ -3,8 +3,7 @@ package nl.nu.performance.api.client.objects;
import nl.nu.performance.api.client.interfaces.Block;
public class HeaderBlock extends Block {
// returns title
public final StyledText component20() {
public final StyledText getTitle() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.shared;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.requests.Route.Method.GET;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -15,10 +16,16 @@ import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
@SuppressWarnings("unused")
public class GmsCoreSupport {
@@ -29,10 +36,24 @@ public class GmsCoreSupport {
= getGmsCoreVendorGroupId() + ".android.gms";
private static final Uri GMS_CORE_PROVIDER
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
private static final String DONT_KILL_MY_APP_LINK
= "https://dontkillmyapp.com";
private static final String DONT_KILL_MY_APP_URL
= "https://dontkillmyapp.com/";
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
= new Route(GET, "/api/v2/{manufacturer}.json");
private static final String DONT_KILL_MY_APP_NAME_PARAMETER
= "?app=MicroG";
private static final String BUILD_MANUFACTURER
= Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-");
/**
* If a manufacturer specific page exists on DontKillMyApp.
*/
@Nullable
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
private static void open(String queryOrLink) {
Logger.printInfo(() -> "Opening link: " + queryOrLink);
Intent intent;
try {
// Check if queryOrLink is a valid URL.
@@ -86,7 +107,7 @@ public class GmsCoreSupport {
// Do not exit. If the app exits before launch completes (and without
// opening another activity), then on some devices such as Pixel phone Android 10
// no toast will be shown and the app will continually be relaunched
// no toast will be shown and the app will continually relaunch
// with the appearance of a hung app.
}
@@ -122,11 +143,12 @@ public class GmsCoreSupport {
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
if (client == null) {
Logger.printInfo(() -> "GmsCore is not running in the background");
checkIfDontKillMyAppSupportsManufacturer();
showBatteryOptimizationDialog(context,
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
"gms_core_dialog_open_website_text",
(dialog, id) -> open(DONT_KILL_MY_APP_LINK));
(dialog, id) -> openDontKillMyApp());
}
}
} catch (Exception ex) {
@@ -141,6 +163,48 @@ public class GmsCoreSupport {
activity.startActivityForResult(intent, 0);
}
private static void checkIfDontKillMyAppSupportsManufacturer() {
Utils.runOnBackgroundThread(() -> {
try {
final long start = System.currentTimeMillis();
HttpURLConnection connection = Requester.getConnectionFromRoute(
DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
final boolean supported = connection.getResponseCode() == 200;
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = supported;
} catch (Exception ex) {
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
+ BUILD_MANUFACTURER, ex);
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = null;
}
});
}
private static void openDontKillMyApp() {
final Boolean manufacturerSupported = DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
String manufacturerPageToOpen;
if (manufacturerSupported == null) {
// Fetch has not completed yet. Only happens on extremely slow internet connections
// and the user spends less than 1 second reading what's on screen.
// Instead of waiting for the fetch (which may timeout),
// open the website without a vendor.
manufacturerPageToOpen = "";
} else if (manufacturerSupported) {
manufacturerPageToOpen = BUILD_MANUFACTURER;
} else {
// No manufacturer specific page exists. Open the general page.
manufacturerPageToOpen = "general";
}
open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER);
}
/**
* @return If GmsCore is not whitelisted from battery optimizations.
*/

View File

@@ -371,7 +371,7 @@ public class Utils {
if (language != AppLanguage.DEFAULT) {
// Create a new context with the desired language.
Logger.printDebug(() -> "Using app language: " + language);
Configuration config = appContext.getResources().getConfiguration();
Configuration config = new Configuration(appContext.getResources().getConfiguration());
config.setLocale(language.getLocale());
context = appContext.createConfigurationContext(config);
}
@@ -391,16 +391,47 @@ public class Utils {
private static Boolean isRightToLeftTextLayout;
/**
* If the device language uses right to left text layout (hebrew, arabic, etc)
* @return If the device language uses right to left text layout (Hebrew, Arabic, etc).
* If this should match any ReVanced language override then instead use
* {@link #isRightToLeftLocale(Locale)} with {@link BaseSettings#REVANCED_LANGUAGE}.
* This is the default locale of the device, which may differ if
* {@link BaseSettings#REVANCED_LANGUAGE} is set to a different language.
*/
public static boolean isRightToLeftTextLayout() {
public static boolean isRightToLeftLocale() {
if (isRightToLeftTextLayout == null) {
String displayLanguage = Locale.getDefault().getDisplayLanguage();
isRightToLeftTextLayout = new Bidi(displayLanguage, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft();
isRightToLeftTextLayout = isRightToLeftLocale(Locale.getDefault());
}
return isRightToLeftTextLayout;
}
/**
* @return If the locale uses right to left text layout (Hebrew, Arabic, etc).
*/
public static boolean isRightToLeftLocale(Locale locale) {
String displayLanguage = locale.getDisplayLanguage();
return new Bidi(displayLanguage, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft();
}
/**
* @return A UTF8 string containing a left-to-right or right-to-left
* character of the device locale. If this should match any ReVanced language
* override then instead use {@link #getTextDirectionString(Locale)} with
* {@link BaseSettings#REVANCED_LANGUAGE}.
*/
public static String getTextDirectionString() {
return getTextDirectionString(isRightToLeftLocale());
}
public static String getTextDirectionString(Locale locale) {
return getTextDirectionString(isRightToLeftLocale(locale));
}
private static String getTextDirectionString(boolean isRightToLeft) {
return isRightToLeft
? "\u200F" // u200F = right to left character.
: "\u200E"; // u200E = left to right character.
}
/**
* @return if the text contains at least 1 number character,
* including any unicode numbers such as Arabic.
@@ -692,9 +723,10 @@ public class Utils {
/**
* Strips all punctuation and converts to lower case. A null parameter returns an empty string.
*/
public static String removePunctuationConvertToLowercase(@Nullable CharSequence original) {
public static String removePunctuationToLowercase(@Nullable CharSequence original) {
if (original == null) return "";
return punctuationPattern.matcher(original).replaceAll("").toLowerCase();
return punctuationPattern.matcher(original).replaceAll("")
.toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale());
}
/**
@@ -726,7 +758,7 @@ public class Utils {
final String sortValue;
switch (preferenceSort) {
case BY_TITLE:
sortValue = removePunctuationConvertToLowercase(preference.getTitle());
sortValue = removePunctuationToLowercase(preference.getTitle());
break;
case BY_KEY:
sortValue = preference.getKey();
@@ -810,4 +842,12 @@ public class Utils {
}
return getResourceColor(colorString);
}
public static int clamp(int value, int lower, int upper) {
return Math.max(lower, Math.min(value, upper));
}
public static float clamp(float value, float lower, float upper) {
return Math.max(lower, Math.min(value, upper));
}
}

View File

@@ -89,9 +89,11 @@ public enum AppLanguage {
ZU;
private final String language;
private final Locale locale;
AppLanguage() {
language = name().toLowerCase(Locale.US);
locale = Locale.forLanguageTag(language);
}
/**
@@ -112,6 +114,6 @@ public enum AppLanguage {
return Locale.getDefault();
}
return Locale.forLanguageTag(language);
return locale;
}
}

View File

@@ -21,6 +21,10 @@ public class NoTitlePreferenceCategory extends PreferenceCategory {
super(context, attrs, defStyleAttr);
}
public NoTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public NoTitlePreferenceCategory(Context context) {
super(context);
}

View File

@@ -0,0 +1,107 @@
package app.revanced.extension.shared.settings.preference;
import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import app.revanced.extension.shared.Utils;
/**
* PreferenceList that sorts itself.
* By default the first entry is preserved in its original position,
* and all other entries are sorted alphabetically.
*
* Ideally the 'keep first entries to preserve' is an xml parameter,
* but currently that's not so simple since Extensions code cannot use
* generated code from the Patches repo (which is required for custom xml parameters).
*
* If any class wants to use a different getFirstEntriesToPreserve value,
* it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}.
*/
@SuppressWarnings({"unused", "deprecation"})
public class SortedListPreference extends ListPreference {
/**
* Sorts the current list entries.
*
* @param firstEntriesToPreserve The number of entries to preserve in their original position.
*/
public void sortEntryAndValues(int firstEntriesToPreserve) {
CharSequence[] entries = getEntries();
CharSequence[] entryValues = getEntryValues();
if (entries == null || entryValues == null) {
return;
}
final int entrySize = entries.length;
if (entrySize != entryValues.length) {
// Xml array declaration has a missing/extra entry.
throw new IllegalStateException();
}
List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve);
SortedMap<String, Pair<CharSequence, CharSequence>> lastEntries = new TreeMap<>();
for (int i = 0; i < entrySize; i++) {
Pair<CharSequence, CharSequence> pair = new Pair<>(entries[i], entryValues[i]);
if (i < firstEntriesToPreserve) {
firstEntries.add(pair);
} else {
lastEntries.put(Utils.removePunctuationToLowercase(pair.first), pair);
}
}
CharSequence[] sortedEntries = new CharSequence[entrySize];
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
int i = 0;
for (Pair<CharSequence, CharSequence> pair : firstEntries) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
for (Pair<CharSequence, CharSequence> pair : lastEntries.values()) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
super.setEntries(sortedEntries);
super.setEntryValues(sortedEntryValues);
}
protected int getFirstEntriesToPreserve() {
return 1;
}
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
sortEntryAndValues(getFirstEntriesToPreserve());
}
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
sortEntryAndValues(getFirstEntriesToPreserve());
}
public SortedListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
sortEntryAndValues(getFirstEntriesToPreserve());
}
public SortedListPreference(Context context) {
super(context);
sortEntryAndValues(getFirstEntriesToPreserve());
}
}

View File

@@ -1,10 +1,18 @@
package app.revanced.extension.youtube;
import static app.revanced.extension.shared.Utils.clamp;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.text.style.ReplacementSpan;
import android.text.TextPaint;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
@@ -121,4 +129,43 @@ public class ThemeHelper {
window.setNavigationBarContrastEnforced(true);
}
}
/**
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
* <p>
* If the factor is greater than 1, the color is lightened by interpolating toward white (#FFFFFF).
* If the factor is less than or equal to 1, the color is darkened by scaling its RGB components toward black (#000000).
* The alpha channel remains unchanged.
*
* @param color The input color to adjust, in ARGB format.
* @param factor The adjustment factor. Use values > 1.0f to lighten (e.g., 1.11f for slight lightening)
* or values <= 1.0f to darken (e.g., 0.95f for slight darkening).
* @return The adjusted color in ARGB format.
*/
public static int adjustColorBrightness(int color, float factor) {
final int alpha = Color.alpha(color);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
if (factor > 1.0f) {
// Lighten: Interpolate toward white (255)
final float t = 1.0f - (1.0f / factor); // Interpolation parameter
red = Math.round(red + (255 - red) * t);
green = Math.round(green + (255 - green) * t);
blue = Math.round(blue + (255 - blue) * t);
} else {
// Darken or no change: Scale toward black
red = (int) (red * factor);
green = (int) (green * factor);
blue = (int) (blue * factor);
}
// Ensure values are within [0, 255]
red = clamp(red, 0, 255);
green = clamp(green, 0, 255);
blue = clamp(blue, 0, 255);
return Color.argb(alpha, red, green, blue);
}
}

View File

@@ -0,0 +1,13 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class HideRelatedVideoOverlayPatch {
/**
* Injection point.
*/
public static boolean hideRelatedVideoOverlay() {
return Settings.HIDE_RELATED_VIDEO_OVERLAY.get();
}
}

View File

@@ -18,7 +18,6 @@ import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@@ -69,13 +68,6 @@ public class ReturnYouTubeDislikePatch {
@Nullable
private static volatile String lastPrefetchedVideoId;
public static void onRYDStatusChange(boolean rydEnabled) {
ReturnYouTubeDislikeApi.resetRateLimits();
// Must remove all values to protect against using stale data
// if the user enables RYD while a video is on screen.
clearData();
}
private static void clearData() {
currentVideoData = null;
lastLithoShortsVideoData = null;
@@ -274,7 +266,7 @@ public class ReturnYouTubeDislikePatch {
Logger.printDebug(() -> "Adding rolling number TextView changes");
view.setCompoundDrawablePadding(ReturnYouTubeDislike.leftSeparatorShapePaddingPixels);
ShapeDrawable separator = ReturnYouTubeDislike.getLeftSeparatorDrawable();
if (Utils.isRightToLeftTextLayout()) {
if (Utils.isRightToLeftLocale()) {
view.setCompoundDrawables(null, null, separator, null);
} else {
view.setCompoundDrawables(separator, null, null, null);

View File

@@ -36,7 +36,7 @@ public final class WideSearchbarPatch {
final int paddingStart = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
8, Resources.getSystem().getDisplayMetrics());
if (Utils.isRightToLeftTextLayout()) {
if (Utils.isRightToLeftLocale()) {
searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom);
} else {
searchBarView.setPadding(paddingStart, paddingTop, paddingRight, paddingBottom);

View File

@@ -34,8 +34,6 @@ public final class LayoutComponentsFilter extends Filter {
private final StringFilterGroup notifyMe;
private final StringFilterGroup singleItemInformationPanel;
private final StringFilterGroup expandableMetadata;
private final ByteArrayFilterGroup searchResultRecommendations;
private final StringFilterGroup searchResultVideo;
private final StringFilterGroup compactChannelBarInner;
private final StringFilterGroup compactChannelBarInnerButton;
private final ByteArrayFilterGroup joinMembershipButton;
@@ -233,14 +231,9 @@ public final class LayoutComponentsFilter extends Filter {
"mixed_content_shelf"
);
searchResultVideo = new StringFilterGroup(
Settings.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
"search_video_with_context.eml"
);
searchResultRecommendations = new ByteArrayFilterGroup(
Settings.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
"endorsement_header_footer"
final var searchResultRecommendationLabels = new StringFilterGroup(
Settings.HIDE_SEARCH_RESULT_RECOMMENDATION_LABELS,
"endorsement_header_footer.eml"
);
horizontalShelves = new StringFilterGroup(
@@ -258,7 +251,7 @@ public final class LayoutComponentsFilter extends Filter {
compactChannelBar,
communityPosts,
paidPromotion,
searchResultVideo,
searchResultRecommendationLabels,
latestPosts,
channelWatermark,
communityGuidelines,
@@ -300,13 +293,6 @@ public final class LayoutComponentsFilter extends Filter {
return false;
}
if (matchedGroup == searchResultVideo) {
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
}
// The groups are excluded from the filter due to the exceptions list below.
// Filter them separately here.
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)

View File

@@ -1,16 +1,12 @@
package app.revanced.extension.youtube.patches.playback.speed;
import static app.revanced.extension.shared.StringRef.sf;
import static app.revanced.extension.shared.StringRef.str;
import android.preference.ListPreference;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.annotation.NonNull;
import java.util.Arrays;
import app.revanced.extension.shared.Logger;
@@ -21,8 +17,6 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class CustomPlaybackSpeedPatch {
private static final float PLAYBACK_SPEED_AUTO = Settings.PLAYBACK_SPEED_DEFAULT.defaultValue;
/**
* Maximum playback speed, exclusive value. Custom speeds must be less than this value.
* <p>
@@ -47,11 +41,6 @@ public class CustomPlaybackSpeedPatch {
*/
private static long lastTimeOldPlaybackMenuInvoked;
/**
* PreferenceList entries and values, of all available playback speeds.
*/
private static String[] preferenceListEntries, preferenceListEntryValues;
static {
final float holdSpeed = Settings.SPEED_TAP_AND_HOLD.get();
@@ -117,33 +106,6 @@ public class CustomPlaybackSpeedPatch {
return false;
}
/**
* Initialize a settings preference list with the available playback speeds.
*/
@SuppressWarnings("deprecation")
public static void initializeListPreference(ListPreference preference) {
if (preferenceListEntries == null) {
final int numberOfEntries = customPlaybackSpeeds.length + 1;
preferenceListEntries = new String[numberOfEntries];
preferenceListEntryValues = new String[numberOfEntries];
// Auto speed (same behavior as unpatched).
preferenceListEntries[0] = sf("revanced_custom_playback_speeds_auto").toString();
preferenceListEntryValues[0] = String.valueOf(PLAYBACK_SPEED_AUTO);
int i = 1;
for (float speed : customPlaybackSpeeds) {
String speedString = String.valueOf(speed);
preferenceListEntries[i] = speedString + "x";
preferenceListEntryValues[i] = speedString;
i++;
}
}
preference.setEntries(preferenceListEntries);
preference.setEntryValues(preferenceListEntryValues);
}
/**
* Injection point.
*/

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.youtube.patches.theme;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.clamp;
import android.content.res.Resources;
import android.graphics.Color;
@@ -378,14 +379,4 @@ public final class SeekbarColorPatch {
return originalColor;
}
}
/** @noinspection SameParameterValue */
private static int clamp(int value, int lower, int upper) {
return Math.max(lower, Math.min(value, upper));
}
/** @noinspection SameParameterValue */
private static float clamp(float value, float lower, float upper) {
return Math.max(lower, Math.min(value, upper));
}
}

View File

@@ -235,7 +235,7 @@ public class ReturnYouTubeDislike {
final boolean compactLayout = Settings.RYD_COMPACT_LAYOUT.get();
if (!compactLayout) {
String leftSeparatorString = getTextDirectionString();
String leftSeparatorString = Utils.getTextDirectionString();
final Spannable leftSeparatorSpan;
if (isRollingNumber) {
leftSeparatorSpan = new SpannableString(leftSeparatorString);
@@ -279,12 +279,6 @@ public class ReturnYouTubeDislike {
return new SpannableString(builder);
}
private static @NonNull String getTextDirectionString() {
return Utils.isRightToLeftTextLayout()
? "\u200F" // u200F = right to left character
: "\u200E"; // u200E = left to right character
}
/**
* @return If the text is likely for a previously created likes/dislikes segmented span.
*/

View File

@@ -0,0 +1,29 @@
package app.revanced.extension.youtube.returnyoutubedislike.ui;
import android.content.Context;
import android.util.AttributeSet;
import app.revanced.extension.youtube.settings.preference.UrlLinkPreference;
/**
* Allows tapping the RYD about preference to open the website.
*/
@SuppressWarnings("unused")
public class ReturnYouTubeDislikeAboutPreference extends UrlLinkPreference {
{
externalUrl = "https://returnyoutubedislike.com";
}
public ReturnYouTubeDislikeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ReturnYouTubeDislikeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ReturnYouTubeDislikeAboutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReturnYouTubeDislikeAboutPreference(Context context) {
super(context);
}
}

View File

@@ -0,0 +1,126 @@
package app.revanced.extension.youtube.returnyoutubedislike.ui;
import static app.revanced.extension.shared.StringRef.str;
import android.content.Context;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
@SuppressWarnings({"unused", "deprecation"})
public class ReturnYouTubeDislikeDebugStatsPreferenceCategory extends PreferenceCategory {
private static final boolean SHOW_RYD_DEBUG_STATS = BaseSettings.DEBUG.get();
private static String createSummaryText(int value, String summaryStringZeroKey, String summaryStringOneOrMoreKey) {
if (value == 0) {
return str(summaryStringZeroKey);
}
return str(summaryStringOneOrMoreKey, value);
}
private static String createMillisecondStringFromNumber(long number) {
return String.format(str("revanced_ryd_statistics_millisecond_text"), number);
}
public ReturnYouTubeDislikeDebugStatsPreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReturnYouTubeDislikeDebugStatsPreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ReturnYouTubeDislikeDebugStatsPreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected View onCreateView(ViewGroup parent) {
if (!SHOW_RYD_DEBUG_STATS) {
// Use an empty view to hide without removing.
return new View(getContext());
}
return super.onCreateView(parent);
}
protected void onAttachedToActivity() {
try {
super.onAttachedToActivity();
if (!SHOW_RYD_DEBUG_STATS) {
return;
}
Logger.printDebug(() -> "Updating stats preferences");
removeAll();
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallResponseTimeAverage_title",
createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeAverage())
);
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallResponseTimeMin_title",
createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMin())
);
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallResponseTimeMax_title",
createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMax())
);
String fetchCallTimeWaitingLastSummary;
final long fetchCallTimeWaitingLast = ReturnYouTubeDislikeApi.getFetchCallResponseTimeLast();
if (fetchCallTimeWaitingLast == ReturnYouTubeDislikeApi.FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT) {
fetchCallTimeWaitingLastSummary = str("revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary");
} else {
fetchCallTimeWaitingLastSummary = createMillisecondStringFromNumber(fetchCallTimeWaitingLast);
}
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallResponseTimeLast_title",
fetchCallTimeWaitingLastSummary
);
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallCount_title",
createSummaryText(ReturnYouTubeDislikeApi.getFetchCallCount(),
"revanced_ryd_statistics_getFetchCallCount_zero_summary",
"revanced_ryd_statistics_getFetchCallCount_non_zero_summary"
)
);
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallNumberOfFailures_title",
createSummaryText(ReturnYouTubeDislikeApi.getFetchCallNumberOfFailures(),
"revanced_ryd_statistics_getFetchCallNumberOfFailures_zero_summary",
"revanced_ryd_statistics_getFetchCallNumberOfFailures_non_zero_summary"
)
);
addStatisticPreference(
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_title",
createSummaryText(ReturnYouTubeDislikeApi.getNumberOfRateLimitRequestsEncountered(),
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_zero_summary",
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"
)
);
} catch (Exception ex) {
Logger.printException(() -> "onAttachedToActivity failure", ex);
}
}
private void addStatisticPreference(String titleKey, String SummaryText) {
Preference statisticPreference = new Preference(getContext());
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str(titleKey));
statisticPreference.setSummary(SummaryText);
addPreference(statisticPreference);
}
}

View File

@@ -11,8 +11,6 @@ import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toolbar;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage;
@@ -21,8 +19,6 @@ import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
import app.revanced.extension.youtube.settings.preference.ReturnYouTubeDislikePreferenceFragment;
import app.revanced.extension.youtube.settings.preference.SponsorBlockPreferenceFragment;
/**
* Hooks LicenseActivity.
@@ -88,28 +84,15 @@ public class LicenseActivityHook {
licenseActivity.setContentView(getResourceIdentifier(
"revanced_settings_with_toolbar", "layout"));
PreferenceFragment fragment;
String toolbarTitleResourceName;
String dataString = Objects.requireNonNull(licenseActivity.getIntent().getDataString());
switch (dataString) {
case "revanced_sb_settings_intent":
toolbarTitleResourceName = "revanced_sb_settings_title";
fragment = new SponsorBlockPreferenceFragment();
break;
case "revanced_ryd_settings_intent":
toolbarTitleResourceName = "revanced_ryd_settings_title";
fragment = new ReturnYouTubeDislikePreferenceFragment();
break;
case "revanced_settings_intent":
toolbarTitleResourceName = "revanced_settings_title";
fragment = new ReVancedPreferenceFragment();
break;
default:
Logger.printException(() -> "Unknown setting: " + dataString);
return;
// Sanity check.
String dataString = licenseActivity.getIntent().getDataString();
if (!"revanced_settings_intent".equals(dataString)) {
Logger.printException(() -> "Unknown intent: " + dataString);
return;
}
createToolbar(licenseActivity, toolbarTitleResourceName);
PreferenceFragment fragment = new ReVancedPreferenceFragment();
createToolbar(licenseActivity, fragment);
//noinspection deprecation
licenseActivity.getFragmentManager()
@@ -122,7 +105,7 @@ public class LicenseActivityHook {
}
@SuppressLint("UseCompatLoadingForDrawables")
private static void createToolbar(Activity activity, String toolbarTitleResourceName) {
private static void createToolbar(Activity activity, PreferenceFragment fragment) {
// Replace dummy placeholder toolbar.
// This is required to fix submenu title alignment issue with Android ASOP 15+
ViewGroup toolBarParent = activity.findViewById(
@@ -134,8 +117,7 @@ public class LicenseActivityHook {
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
toolbar.setBackgroundColor(ThemeHelper.getToolbarBackgroundColor());
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> activity.onBackPressed());
toolbar.setTitle(getResourceIdentifier(toolbarTitleResourceName, "string"));
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16,
Utils.getContext().getResources().getDisplayMetrics());
@@ -148,6 +130,11 @@ public class LicenseActivityHook {
}
setToolbarLayoutParams(toolbar);
// Add Search Icon and EditText for ReVancedPreferenceFragment only.
if (fragment instanceof ReVancedPreferenceFragment) {
SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment);
}
toolBarParent.addView(toolbar, 0);
}
}
}

View File

@@ -0,0 +1,381 @@
package app.revanced.extension.youtube.settings;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
/**
* Controller for managing the search view in ReVanced settings.
*/
@SuppressWarnings({"deprecated", "DiscouragedApi"})
public class SearchViewController {
private static final int MAX_HISTORY_SIZE = 5;
private final SearchView searchView;
private final FrameLayout searchContainer;
private final Toolbar toolbar;
private final Activity activity;
private boolean isSearchActive;
private final CharSequence originalTitle;
private final Deque<String> searchHistory;
private final AutoCompleteTextView autoCompleteTextView;
private final boolean showSettingsSearchHistory;
/**
* Creates a background drawable for the SearchView with rounded corners.
*/
private static GradientDrawable createBackgroundDrawable(Context context) {
GradientDrawable background = new GradientDrawable();
background.setShape(GradientDrawable.RECTANGLE);
background.setCornerRadius(28 * context.getResources().getDisplayMetrics().density); // 28dp corner radius.
int baseColor = ThemeHelper.getBackgroundColor();
int adjustedColor = ThemeHelper.isDarkTheme()
? ThemeHelper.adjustColorBrightness(baseColor, 1.11f) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
background.setColor(adjustedColor);
return background;
}
/**
* Creates a background drawable for suggestion items with rounded corners.
*/
private static GradientDrawable createSuggestionBackgroundDrawable(Context context) {
GradientDrawable background = new GradientDrawable();
background.setShape(GradientDrawable.RECTANGLE);
background.setCornerRadius(8 * context.getResources().getDisplayMetrics().density); // 8dp corner radius.
return background;
}
/**
* Adds search view components to the activity.
*/
public static void addSearchViewComponents(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
new SearchViewController(activity, toolbar, fragment);
}
private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
this.activity = activity;
this.toolbar = toolbar;
this.originalTitle = toolbar.getTitle();
this.showSettingsSearchHistory = Settings.SETTINGS_SEARCH_HISTORY.get();
this.searchHistory = new LinkedList<>();
StringSetting searchEntries = Settings.SETTINGS_SEARCH_ENTRIES;
if (showSettingsSearchHistory) {
String entries = searchEntries.get();
if (!entries.isBlank()) {
searchHistory.addAll(Arrays.asList(entries.split("\n")));
}
} else {
// Clear old saved history if the user turns off the feature.
searchEntries.resetToDefault();
}
// Retrieve SearchView and container from XML.
searchView = activity.findViewById(getResourceIdentifier(
"revanced_search_view", "id"));
searchContainer = activity.findViewById(getResourceIdentifier(
"revanced_search_view_container", "id"));
// Initialize AutoCompleteTextView.
autoCompleteTextView = searchView.findViewById(
searchView.getContext().getResources().getIdentifier(
"android:id/search_src_text", null, null));
// Set background and query hint.
searchView.setBackground(createBackgroundDrawable(toolbar.getContext()));
searchView.setQueryHint(str("revanced_settings_search_hint"));
// Configure RTL support based on app language.
AppLanguage appLanguage = BaseSettings.REVANCED_LANGUAGE.get();
if (Utils.isRightToLeftLocale(appLanguage.getLocale())) {
searchView.setTextDirection(View.TEXT_DIRECTION_RTL);
searchView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
}
// Set up search history suggestions.
if (showSettingsSearchHistory) {
setupSearchHistory();
}
// Set up query text listener.
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
try {
String queryTrimmed = query.trim();
if (!queryTrimmed.isEmpty()) {
saveSearchQuery(queryTrimmed);
}
// Hide suggestions on submit.
if (showSettingsSearchHistory && autoCompleteTextView != null) {
autoCompleteTextView.dismissDropDown();
}
} catch (Exception ex) {
Logger.printException(() -> "onQueryTextSubmit failure", ex);
}
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
try {
Logger.printDebug(() -> "Search query: " + newText);
fragment.filterPreferences(newText);
// Prevent suggestions from showing during text input.
if (showSettingsSearchHistory && autoCompleteTextView != null) {
if (!newText.isEmpty()) {
autoCompleteTextView.dismissDropDown();
autoCompleteTextView.setThreshold(Integer.MAX_VALUE); // Disable autocomplete suggestions.
} else {
autoCompleteTextView.setThreshold(1); // Re-enable for empty input.
}
}
} catch (Exception ex) {
Logger.printException(() -> "onQueryTextChange failure", ex);
}
return true;
}
});
// Set menu and search icon.
final int actionSearchId = getResourceIdentifier("action_search", "id");
toolbar.inflateMenu(getResourceIdentifier("revanced_search_menu", "menu"));
MenuItem searchItem = toolbar.getMenu().findItem(actionSearchId);
searchItem.setIcon(getResourceIdentifier(ThemeHelper.isDarkTheme()
? "yt_outline_search_white_24"
: "yt_outline_search_black_24",
"drawable")).setTooltipText(null);
// Set menu item click listener.
toolbar.setOnMenuItemClickListener(item -> {
try {
if (item.getItemId() == actionSearchId) {
if (!isSearchActive) {
openSearch();
}
return true;
}
} catch (Exception ex) {
Logger.printException(() -> "menu click failure", ex);
}
return false;
});
// Set navigation click listener.
toolbar.setNavigationOnClickListener(view -> {
try {
if (isSearchActive) {
closeSearch();
} else {
activity.onBackPressed();
}
} catch (Exception ex) {
Logger.printException(() -> "navigation click failure", ex);
}
});
}
/**
* Sets up the search history suggestions for the SearchView with custom adapter.
*/
private void setupSearchHistory() {
if (autoCompleteTextView != null) {
SearchHistoryAdapter adapter = new SearchHistoryAdapter(activity, new ArrayList<>(searchHistory));
autoCompleteTextView.setAdapter(adapter);
autoCompleteTextView.setThreshold(1); // Initial threshold for empty input.
autoCompleteTextView.setLongClickable(true);
// Show suggestions only when search bar is active and query is empty.
autoCompleteTextView.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus && isSearchActive && autoCompleteTextView.getText().length() == 0) {
autoCompleteTextView.showDropDown();
}
});
}
}
/**
* Saves a search query to the search history.
* @param query The search query to save.
*/
private void saveSearchQuery(String query) {
if (!showSettingsSearchHistory) {
return;
}
searchHistory.remove(query); // Remove if already exists to update position.
searchHistory.addFirst(query); // Add to the most recent.
// Remove extra old entries.
while (searchHistory.size() > MAX_HISTORY_SIZE) {
String last = searchHistory.removeLast();
Logger.printDebug(() -> "Removing search history query: " + last);
}
saveSearchHistory();
updateSearchHistoryAdapter();
}
/**
* Removes a search query from the search history.
* @param query The search query to remove.
*/
private void removeSearchQuery(String query) {
searchHistory.remove(query);
saveSearchHistory();
updateSearchHistoryAdapter();
}
/**
* Save the search history to the shared preferences.
*/
private void saveSearchHistory() {
Logger.printDebug(() -> "Saving search history: " + searchHistory);
Settings.SETTINGS_SEARCH_ENTRIES.save(
String.join("\n", searchHistory)
);
}
/**
* Updates the search history adapter with the latest history.
*/
private void updateSearchHistoryAdapter() {
if (autoCompleteTextView == null) {
return;
}
SearchHistoryAdapter adapter = (SearchHistoryAdapter) autoCompleteTextView.getAdapter();
if (adapter != null) {
adapter.clear();
adapter.addAll(searchHistory);
adapter.notifyDataSetChanged();
}
}
/**
* Opens the search view and shows the keyboard.
*/
private void openSearch() {
isSearchActive = true;
toolbar.getMenu().findItem(getResourceIdentifier(
"action_search", "id")).setVisible(false);
toolbar.setTitle("");
searchContainer.setVisibility(View.VISIBLE);
searchView.requestFocus();
// Show keyboard.
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT);
// Show suggestions with a slight delay.
if (showSettingsSearchHistory && autoCompleteTextView != null && autoCompleteTextView.getText().length() == 0) {
searchView.postDelayed(() -> {
if (isSearchActive && autoCompleteTextView.getText().length() == 0) {
autoCompleteTextView.showDropDown();
}
}, 100); // 100ms delay to ensure focus is stable.
}
}
/**
* Closes the search view and hides the keyboard.
*/
private void closeSearch() {
isSearchActive = false;
toolbar.getMenu().findItem(getResourceIdentifier(
"action_search", "id"))
.setIcon(getResourceIdentifier(ThemeHelper.isDarkTheme()
? "yt_outline_search_white_24"
: "yt_outline_search_black_24",
"drawable")
).setVisible(true);
toolbar.setTitle(originalTitle);
searchContainer.setVisibility(View.GONE);
searchView.setQuery("", false);
// Hide keyboard.
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchView.getWindowToken(), 0);
}
/**
* Custom ArrayAdapter for search history.
*/
private class SearchHistoryAdapter extends ArrayAdapter<String> {
public SearchHistoryAdapter(Context context, List<String> history) {
super(context, 0, history);
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) {
if (convertView == null) {
convertView = LinearLayout.inflate(getContext(), getResourceIdentifier(
"revanced_search_suggestion_item", "layout"), null);
}
// Apply rounded corners programmatically.
convertView.setBackground(createSuggestionBackgroundDrawable(getContext()));
String query = getItem(position);
// Set query text.
TextView textView = convertView.findViewById(getResourceIdentifier(
"suggestion_text", "id"));
if (textView != null) {
textView.setText(query);
}
// Set click listener for inserting query into SearchView.
convertView.setOnClickListener(v -> {
searchView.setQuery(query, true); // Insert selected query and submit.
});
// Set long click listener for deletion confirmation.
convertView.setOnLongClickListener(v -> {
new AlertDialog.Builder(activity)
.setTitle(query)
.setMessage(str("revanced_settings_search_remove_message"))
.setPositiveButton(android.R.string.ok,
(dialog, which) -> removeSearchQuery(query))
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
});
return convertView;
}
}
}

View File

@@ -25,6 +25,8 @@ import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehavi
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
import android.graphics.Color;
@@ -103,7 +105,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE);
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATIONS = new BooleanSetting("revanced_hide_search_result_recommendations", TRUE);
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_search_result_recommendation_labels", TRUE);
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
// Alternative thumbnails
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
@@ -139,6 +141,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE);
public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true);
public static final BooleanSetting HIDE_RELATED_VIDEO_OVERLAY = new BooleanSetting("revanced_hide_related_video_overlay", FALSE, true);
public static final BooleanSetting HIDE_HIDE_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_channel_guidelines", TRUE);
public static final BooleanSetting HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
@@ -217,6 +220,8 @@ public class Settings extends BaseSettings {
// General layout
public static final BooleanSetting RESTORE_OLD_SETTINGS_MENUS = new BooleanSetting("revanced_restore_old_settings_menus", FALSE, true);
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "", true);
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
@@ -340,19 +345,17 @@ public class Settings extends BaseSettings {
public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", FALSE, true, parent(SWIPE_BRIGHTNESS));
// ReturnYoutubeDislike
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("ryd_enabled", TRUE);
public static final StringSetting RYD_USER_ID = new StringSetting("ryd_user_id", "", false, false);
public static final BooleanSetting RYD_SHORTS = new BooleanSetting("ryd_shorts", TRUE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("ryd_dislike_percentage", FALSE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("ryd_compact_layout", FALSE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("ryd_estimated_like", TRUE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("ryd_toast_on_connection_error", TRUE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE);
public static final StringSetting RYD_USER_ID = new StringSetting("revanced_ryd_user_id", "", false, false);
public static final BooleanSetting RYD_SHORTS = new BooleanSetting("revanced_ryd_shorts", TRUE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("revanced_ryd_dislike_percentage", FALSE, true, parent(RYD_ENABLED));
public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("revanced_ryd_compact_layout", FALSE, true, parent(RYD_ENABLED));
public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("revanced_ryd_estimated_like", TRUE, true, parent(RYD_ENABLED));
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("revanced_ryd_toast_on_connection_error", TRUE, parent(RYD_ENABLED));
// SponsorBlock
public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE);
/**
* Do not use directly, instead use {@link SponsorBlockSettings}
*/
/** Do not use id setting directly. Instead use {@link SponsorBlockSettings}. */
public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", "");
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
@@ -416,12 +419,10 @@ public class Settings extends BaseSettings {
// region Migration
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_BUTTONS, HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS);
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
migrateOldSettingToNew(DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN, HIDE_END_SCREEN_SUGGESTED_VIDEO);
migrateOldSettingToNew(DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU, ADVANCED_VIDEO_QUALITY_MENU);
migrateOldSettingToNew(DEPRECATED_AUTO_CAPTIONS, DISABLE_AUTO_CAPTIONS);
// Migrate renamed enum.
//noinspection deprecation
@@ -464,10 +465,15 @@ public class Settings extends BaseSettings {
SPOOF_APP_VERSION_TARGET.resetToDefault();
}
if (!DEPRECATED_AUTO_CAPTIONS.isSetToDefault()) {
DISABLE_AUTO_CAPTIONS.save(true);
DEPRECATED_AUTO_CAPTIONS.resetToDefault();
}
// RYD requires manually migrating old settings since the lack of
// a "revanced_" on the old setting causes duplicate key exceptions during export.
SharedPrefCategory revancedPrefs = Setting.preferences;
Setting.migrateFromOldPreferences(revancedPrefs, RYD_USER_ID, "ryd_user_id");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_ENABLED, "ryd_enabled");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_DISLIKE_PERCENTAGE, "ryd_dislike_percentage");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_COMPACT_LAYOUT, "ryd_compact_layout");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_ESTIMATED_LIKE, "ryd_estimated_like");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_TOAST_ON_CONNECTION_ERROR, "ryd_toast_on_connection_error");
// endregion
@@ -478,4 +484,3 @@ public class Settings extends BaseSettings {
// endregion
}
}

View File

@@ -1,23 +1,15 @@
package app.revanced.extension.youtube.settings.preference;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.preference.Preference;
import android.util.AttributeSet;
/**
* Allows tapping the DeArrow about preference to open the DeArrow website.
*/
@SuppressWarnings({"unused", "deprecation"})
public class AlternativeThumbnailsAboutDeArrowPreference extends Preference {
@SuppressWarnings("unused")
public class AlternativeThumbnailsAboutDeArrowPreference extends UrlLinkPreference {
{
setOnPreferenceClickListener(pref -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://dearrow.ajay.app"));
pref.getContext().startActivity(i);
return false;
});
externalUrl = "https://dearrow.ajay.app";
}
public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

View File

@@ -0,0 +1,62 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.sf;
import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings;
/**
* Custom video speeds used by {@link CustomPlaybackSpeedPatch}.
*/
@SuppressWarnings({"unused", "deprecation"})
public final class CustomVideoSpeedListPreference extends ListPreference {
/**
* Initialize a settings preference list with the available playback speeds.
*/
private void initializeEntryValues() {
float[] customPlaybackSpeeds = CustomPlaybackSpeedPatch.customPlaybackSpeeds;
final int numberOfEntries = customPlaybackSpeeds.length + 1;
String[] preferenceListEntries = new String[numberOfEntries];
String[] preferenceListEntryValues = new String[numberOfEntries];
// Auto speed (same behavior as unpatched).
preferenceListEntries[0] = sf("revanced_custom_playback_speeds_auto").toString();
preferenceListEntryValues[0] = String.valueOf(Settings.PLAYBACK_SPEED_DEFAULT.defaultValue);
int i = 1;
for (float speed : customPlaybackSpeeds) {
String speedString = String.valueOf(speed);
preferenceListEntries[i] = speedString + "x";
preferenceListEntryValues[i] = speedString;
i++;
}
setEntries(preferenceListEntries);
setEntryValues(preferenceListEntryValues);
}
{
initializeEntryValues();
}
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomVideoSpeedListPreference(Context context) {
super(context);
}
}

View File

@@ -19,12 +19,15 @@ public class HtmlPreference extends Preference {
public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public HtmlPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HtmlPreference(Context context) {
super(context);
}

View File

@@ -1,5 +1,6 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.annotation.SuppressLint;
@@ -9,34 +10,66 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.util.Pair;
import android.preference.SwitchPreference;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.EnumSetting;
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.LicenseActivityHook;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
/**
* Preference fragment for ReVanced settings.
*
* @noinspection deprecation
*/
@SuppressWarnings("deprecation")
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
/**
* The main PreferenceScreen used to display the current set of preferences.
* This screen is manipulated during initialization and filtering to show or hide preferences.
*/
private PreferenceScreen preferenceScreen;
/**
* A copy of the original PreferenceScreen created during initialization.
* Used to restore the preference structure to its initial state after filtering or other modifications.
*/
private PreferenceScreen originalPreferenceScreen;
/**
* Used for searching preferences. A Collection of all preferences including nested preferences.
* Root preferences are excluded (no need to search what's on the root screen),
* but their sub preferences are included.
*/
private final List<AbstractPreferenceSearchData<?>> allPreferences = new ArrayList<>();
@SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() {
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
@@ -47,85 +80,140 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
}
/**
* Sorts a preference list by menu entries, but preserves the first value as the first entry.
*
* @noinspection SameParameterValue
* Initializes the preference fragment, copying the original screen to allow full restoration.
*/
private static void sortListPreferenceByValues(ListPreference listPreference, int firstEntriesToPreserve) {
CharSequence[] entries = listPreference.getEntries();
CharSequence[] entryValues = listPreference.getEntryValues();
final int entrySize = entries.length;
if (entrySize != entryValues.length) {
// Xml array declaration has a missing/extra entry.
throw new IllegalStateException();
}
List<Pair<String, String>> firstPairs = new ArrayList<>(firstEntriesToPreserve);
List<Pair<String, String>> pairsToSort = new ArrayList<>(entrySize);
for (int i = 0; i < entrySize; i++) {
Pair<String, String> pair = new Pair<>(entries[i].toString(), entryValues[i].toString());
if (i < firstEntriesToPreserve) {
firstPairs.add(pair);
} else {
pairsToSort.add(pair);
}
}
pairsToSort.sort((pair1, pair2)
-> pair1.first.compareToIgnoreCase(pair2.first));
CharSequence[] sortedEntries = new CharSequence[entrySize];
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
int i = 0;
for (Pair<String, String> pair : firstPairs) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
for (Pair<String, String> pair : pairsToSort) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
listPreference.setEntries(sortedEntries);
listPreference.setEntryValues(sortedEntryValues);
}
@Override
protected void initialize() {
super.initialize();
try {
setPreferenceScreenToolbar(getPreferenceScreen());
preferenceScreen = getPreferenceScreen();
Utils.sortPreferenceGroups(preferenceScreen);
// If the preference was included, then initialize it based on the available playback speed.
Preference preference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key);
if (preference instanceof ListPreference playbackPreference) {
CustomPlaybackSpeedPatch.initializeListPreference(playbackPreference);
// Store the original structure for restoration after filtering.
originalPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
for (int i = 0, count = preferenceScreen.getPreferenceCount(); i < count; i++) {
originalPreferenceScreen.addPreference(preferenceScreen.getPreference(i));
}
sortPreferenceListMenu(Settings.CHANGE_START_PAGE);
sortPreferenceListMenu(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE);
sortPreferenceListMenu(BaseSettings.REVANCED_LANGUAGE);
setPreferenceScreenToolbar(preferenceScreen);
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
private void sortPreferenceListMenu(EnumSetting<?> setting) {
Preference preference = findPreference(setting.key);
if (preference instanceof ListPreference languagePreference) {
sortListPreferenceByValues(languagePreference, 1);
/**
* Called when the fragment starts, ensuring all preferences are collected after initialization.
*/
@Override
public void onStart() {
super.onStart();
try {
if (allPreferences.isEmpty()) {
// Must collect preferences on start and not in initialize since
// legacy SB settings are not loaded yet.
Logger.printDebug(() -> "Collecting preferences to search");
// Do not show root menu preferences in search results.
// Instead search for everything that's not shown when search is not active.
collectPreferences(preferenceScreen, 1, 0);
}
} catch (Exception ex) {
Logger.printException(() -> "onStart failure", ex);
}
}
/**
* Recursively collects all preferences from the screen or group.
* @param includeDepth Menu depth to start including preferences.
* A value of 0 adds all preferences.
*/
private void collectPreferences(PreferenceGroup group, int includeDepth, int currentDepth) {
for (int i = 0, count = group.getPreferenceCount(); i < count; i++) {
Preference preference = group.getPreference(i);
if (includeDepth <= currentDepth && !(preference instanceof PreferenceCategory)
&& !(preference instanceof SponsorBlockPreferenceGroup)) {
AbstractPreferenceSearchData<?> data;
if (preference instanceof SwitchPreference switchPref) {
data = new SwitchPreferenceSearchData(switchPref);
} else if (preference instanceof ListPreference listPref) {
data = new ListPreferenceSearchData(listPref);
} else {
data = new PreferenceSearchData(preference);
}
allPreferences.add(data);
}
if (preference instanceof PreferenceGroup subGroup) {
collectPreferences(subGroup, includeDepth, currentDepth + 1);
}
}
}
/**
* Filters the preferences using the given query string and applies highlighting.
*/
public void filterPreferences(String query) {
preferenceScreen.removeAll();
if (TextUtils.isEmpty(query)) {
// Restore original preferences and their titles/summaries/entries.
for (int i = 0, count = originalPreferenceScreen.getPreferenceCount(); i < count; i++) {
preferenceScreen.addPreference(originalPreferenceScreen.getPreference(i));
}
for (AbstractPreferenceSearchData<?> data : allPreferences) {
data.clearHighlighting();
}
return;
}
// Navigation path -> Category
Map<String, PreferenceCategory> categoryMap = new HashMap<>();
String queryLower = Utils.removePunctuationToLowercase(query);
Pattern queryPattern = Pattern.compile(Pattern.quote(Utils.removePunctuationToLowercase(query)),
Pattern.CASE_INSENSITIVE);
for (AbstractPreferenceSearchData<?> data : allPreferences) {
if (data.matchesSearchQuery(queryLower)) {
data.applyHighlighting(queryLower, queryPattern);
String navigationPath = data.navigationPath;
PreferenceCategory group = categoryMap.computeIfAbsent(navigationPath, key -> {
PreferenceCategory newGroup = new PreferenceCategory(preferenceScreen.getContext());
newGroup.setTitle(navigationPath);
preferenceScreen.addPreference(newGroup);
return newGroup;
});
group.addPreference(data.preference);
}
}
// Show 'No results found' if search results are empty.
if (categoryMap.isEmpty()) {
Preference noResultsPreference = new Preference(preferenceScreen.getContext());
noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query));
noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary"));
noResultsPreference.setSelectable(false);
// Set icon for the placeholder preference.
noResultsPreference.setLayoutResource(getResourceIdentifier(
"revanced_preference_with_icon_no_search_result", "layout"));
noResultsPreference.setIcon(getResourceIdentifier(
ThemeHelper.isDarkTheme() ? "yt_outline_search_white_24" : "yt_outline_search_black_24",
"drawable"));
preferenceScreen.addPreference(noResultsPreference);
}
}
/**
* Sets toolbar for all nested preference screens.
*/
private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
for (int i = 0, preferenceCount = parentScreen.getPreferenceCount(); i < preferenceCount; i++) {
for (int i = 0, count = parentScreen.getPreferenceCount(); i < count; i++) {
Preference childPreference = parentScreen.getPreference(i);
if (childPreference instanceof PreferenceScreen) {
// Recursively set sub preferences.
@@ -156,6 +244,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
toolbar.setTitle(childScreen.getTitle());
toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
final int margin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
);
@@ -177,3 +266,271 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
}
}
}
@SuppressWarnings("deprecation")
class AbstractPreferenceSearchData<T extends Preference> {
/**
* @return The navigation path for the given preference, such as "Player > Action buttons".
*/
private static String getPreferenceNavigationString(Preference preference) {
Deque<CharSequence> pathElements = new ArrayDeque<>();
while (true) {
preference = preference.getParent();
if (preference == null) {
if (pathElements.isEmpty()) {
return "";
}
Locale locale = BaseSettings.REVANCED_LANGUAGE.get().getLocale();
return Utils.getTextDirectionString(locale) + String.join(" > ", pathElements);
}
if (!(preference instanceof NoTitlePreferenceCategory)
&& !(preference instanceof SponsorBlockPreferenceGroup)) {
CharSequence title = preference.getTitle();
if (title != null && title.length() > 0) {
pathElements.addFirst(title);
}
}
}
}
/**
* Highlights the search query in the given text by applying color span.
* @param text The original text to process.
* @param queryPattern The search query to highlight.
* @return The text with highlighted query matches as a SpannableStringBuilder.
*/
static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
if (TextUtils.isEmpty(text)) {
return text;
}
final int baseColor = ThemeHelper.getBackgroundColor();
final int adjustedColor = ThemeHelper.isDarkTheme()
? ThemeHelper.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor);
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
Matcher matcher = queryPattern.matcher(text);
while (matcher.find()) {
spannable.setSpan(
highlightSpan,
matcher.start(),
matcher.end(),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
);
}
return spannable;
}
final T preference;
final String key;
final String navigationPath;
boolean highlightingApplied;
@Nullable
CharSequence originalTitle;
@Nullable
String searchTitle;
AbstractPreferenceSearchData(T pref) {
preference = pref;
key = Utils.removePunctuationToLowercase(pref.getKey());
navigationPath = getPreferenceNavigationString(pref);
}
@CallSuper
void updateSearchDataIfNeeded() {
if (highlightingApplied) {
// Must clear, otherwise old highlighting is still applied.
clearHighlighting();
}
CharSequence title = preference.getTitle();
if (originalTitle != title) { // Check using reference equality.
originalTitle = title;
searchTitle = Utils.removePunctuationToLowercase(title);
}
}
@CallSuper
boolean matchesSearchQuery(String query) {
updateSearchDataIfNeeded();
return key.contains(query)
|| searchTitle != null && searchTitle.contains(query);
}
@CallSuper
void applyHighlighting(String query, Pattern queryPattern) {
preference.setTitle(highlightSearchQuery(originalTitle, queryPattern));
highlightingApplied = true;
}
@CallSuper
void clearHighlighting() {
if (highlightingApplied) {
preference.setTitle(originalTitle);
highlightingApplied = false;
}
}
}
/**
* Regular preference type that only uses the base preference summary.
* Should only be used if a more specific data class does not exist.
*/
@SuppressWarnings("deprecation")
class PreferenceSearchData extends AbstractPreferenceSearchData<Preference> {
@Nullable
CharSequence originalSummary;
@Nullable
String searchSummary;
PreferenceSearchData(Preference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence summary = preference.getSummary();
if (originalSummary != summary) {
originalSummary = summary;
searchSummary = Utils.removePunctuationToLowercase(summary);
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchSummary != null && searchSummary.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
preference.setSummary(highlightSearchQuery(originalSummary, queryPattern));
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setSummary(originalSummary);
}
}
/**
* Switch preference type that uses summaryOn and summaryOff.
*/
@SuppressWarnings("deprecation")
class SwitchPreferenceSearchData extends AbstractPreferenceSearchData<SwitchPreference> {
@Nullable
CharSequence originalSummaryOn, originalSummaryOff;
@Nullable
String searchSummaryOn, searchSummaryOff;
SwitchPreferenceSearchData(SwitchPreference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence summaryOn = preference.getSummaryOn();
if (originalSummaryOn != summaryOn) {
originalSummaryOn = summaryOn;
searchSummaryOn = Utils.removePunctuationToLowercase(summaryOn);
}
CharSequence summaryOff = preference.getSummaryOff();
if (originalSummaryOff != summaryOff) {
originalSummaryOff = summaryOff;
searchSummaryOff = Utils.removePunctuationToLowercase(summaryOff);
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchSummaryOn != null && searchSummaryOn.contains(query)
|| searchSummaryOff != null && searchSummaryOff.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
preference.setSummaryOn(highlightSearchQuery(originalSummaryOn, queryPattern));
preference.setSummaryOff(highlightSearchQuery(originalSummaryOff, queryPattern));
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setSummaryOn(originalSummaryOn);
preference.setSummaryOff(originalSummaryOff);
}
}
/**
* List preference type that uses entries.
*/
@SuppressWarnings("deprecation")
class ListPreferenceSearchData extends AbstractPreferenceSearchData<ListPreference> {
@Nullable
CharSequence[] originalEntries;
@Nullable
String searchEntries;
ListPreferenceSearchData(ListPreference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence[] entries = preference.getEntries();
if (originalEntries != entries) {
originalEntries = entries;
searchEntries = Utils.removePunctuationToLowercase(String.join(" ", entries));
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchEntries != null && searchEntries.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
if (originalEntries != null) {
final int length = originalEntries.length;
CharSequence[] highlightedEntries = new CharSequence[length];
for (int i = 0; i < length; i++) {
highlightedEntries[i] = highlightSearchQuery(originalEntries[i], queryPattern);
// Cannot highlight the summary text, because ListPreference uses
// the toString() of the summary CharSequence which strips away all formatting.
}
preference.setEntries(highlightedEntries);
}
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setEntries(originalEntries);
}
}

View File

@@ -1,257 +0,0 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.patches.ReturnYouTubeDislikePatch;
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
import app.revanced.extension.youtube.settings.Settings;
/** @noinspection deprecation*/
public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
/**
* If dislikes are shown on Shorts.
*/
private SwitchPreference shortsPreference;
/**
* If dislikes are shown as percentage.
*/
private SwitchPreference percentagePreference;
/**
* If segmented like/dislike button uses smaller compact layout.
*/
private SwitchPreference compactLayoutPreference;
/**
* If hidden likes are replaced with an estimated value.
*/
private SwitchPreference estimatedLikesPreference;
/**
* If segmented like/dislike button uses smaller compact layout.
*/
private SwitchPreference toastOnRYDNotAvailable;
private void updateUIState() {
shortsPreference.setEnabled(Settings.RYD_SHORTS.isAvailable());
percentagePreference.setEnabled(Settings.RYD_DISLIKE_PERCENTAGE.isAvailable());
compactLayoutPreference.setEnabled(Settings.RYD_COMPACT_LAYOUT.isAvailable());
estimatedLikesPreference.setEnabled(Settings.RYD_ESTIMATED_LIKE.isAvailable());
toastOnRYDNotAvailable.setEnabled(Settings.RYD_TOAST_ON_CONNECTION_ERROR.isAvailable());
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
Activity context = getActivity();
PreferenceManager manager = getPreferenceManager();
manager.setSharedPreferencesName(Setting.preferences.name);
PreferenceScreen preferenceScreen = manager.createPreferenceScreen(context);
setPreferenceScreen(preferenceScreen);
SwitchPreference enabledPreference = new SwitchPreference(context);
enabledPreference.setChecked(Settings.RYD_ENABLED.get());
enabledPreference.setTitle(str("revanced_ryd_enable_title"));
enabledPreference.setSummaryOn(str("revanced_ryd_enable_summary_on"));
enabledPreference.setSummaryOff(str("revanced_ryd_enable_summary_off"));
enabledPreference.setOnPreferenceChangeListener((pref, newValue) -> {
final Boolean rydIsEnabled = (Boolean) newValue;
Settings.RYD_ENABLED.save(rydIsEnabled);
ReturnYouTubeDislikePatch.onRYDStatusChange(rydIsEnabled);
updateUIState();
return true;
});
preferenceScreen.addPreference(enabledPreference);
shortsPreference = new SwitchPreference(context);
shortsPreference.setChecked(Settings.RYD_SHORTS.get());
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
String shortsSummary = str("revanced_ryd_shorts_summary_on_disclaimer");
shortsPreference.setSummaryOn(shortsSummary);
shortsPreference.setSummaryOff(str("revanced_ryd_shorts_summary_off"));
shortsPreference.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_SHORTS.save((Boolean) newValue);
updateUIState();
return true;
});
preferenceScreen.addPreference(shortsPreference);
percentagePreference = new SwitchPreference(context);
percentagePreference.setChecked(Settings.RYD_DISLIKE_PERCENTAGE.get());
percentagePreference.setTitle(str("revanced_ryd_dislike_percentage_title"));
percentagePreference.setSummaryOn(str("revanced_ryd_dislike_percentage_summary_on"));
percentagePreference.setSummaryOff(str("revanced_ryd_dislike_percentage_summary_off"));
percentagePreference.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_DISLIKE_PERCENTAGE.save((Boolean) newValue);
ReturnYouTubeDislike.clearAllUICaches();
updateUIState();
return true;
});
preferenceScreen.addPreference(percentagePreference);
compactLayoutPreference = new SwitchPreference(context);
compactLayoutPreference.setChecked(Settings.RYD_COMPACT_LAYOUT.get());
compactLayoutPreference.setTitle(str("revanced_ryd_compact_layout_title"));
compactLayoutPreference.setSummaryOn(str("revanced_ryd_compact_layout_summary_on"));
compactLayoutPreference.setSummaryOff(str("revanced_ryd_compact_layout_summary_off"));
compactLayoutPreference.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_COMPACT_LAYOUT.save((Boolean) newValue);
ReturnYouTubeDislike.clearAllUICaches();
updateUIState();
return true;
});
preferenceScreen.addPreference(compactLayoutPreference);
estimatedLikesPreference = new SwitchPreference(context);
estimatedLikesPreference.setChecked(Settings.RYD_ESTIMATED_LIKE.get());
estimatedLikesPreference.setTitle(str("revanced_ryd_estimated_like_title"));
estimatedLikesPreference.setSummaryOn(str("revanced_ryd_estimated_like_summary_on"));
estimatedLikesPreference.setSummaryOff(str("revanced_ryd_estimated_like_summary_off"));
estimatedLikesPreference.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_ESTIMATED_LIKE.save((Boolean) newValue);
ReturnYouTubeDislike.clearAllUICaches();
updateUIState();
return true;
});
preferenceScreen.addPreference(estimatedLikesPreference);
toastOnRYDNotAvailable = new SwitchPreference(context);
toastOnRYDNotAvailable.setChecked(Settings.RYD_TOAST_ON_CONNECTION_ERROR.get());
toastOnRYDNotAvailable.setTitle(str("revanced_ryd_toast_on_connection_error_title"));
toastOnRYDNotAvailable.setSummaryOn(str("revanced_ryd_toast_on_connection_error_summary_on"));
toastOnRYDNotAvailable.setSummaryOff(str("revanced_ryd_toast_on_connection_error_summary_off"));
toastOnRYDNotAvailable.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_TOAST_ON_CONNECTION_ERROR.save((Boolean) newValue);
updateUIState();
return true;
});
preferenceScreen.addPreference(toastOnRYDNotAvailable);
updateUIState();
// About category
PreferenceCategory aboutCategory = new PreferenceCategory(context);
aboutCategory.setTitle(str("revanced_ryd_about"));
preferenceScreen.addPreference(aboutCategory);
// ReturnYouTubeDislike Website
Preference aboutWebsitePreference = new Preference(context);
aboutWebsitePreference.setTitle(str("revanced_ryd_attribution_title"));
aboutWebsitePreference.setSummary(str("revanced_ryd_attribution_summary"));
aboutWebsitePreference.setOnPreferenceClickListener(pref -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://returnyoutubedislike.com"));
pref.getContext().startActivity(i);
return false;
});
aboutCategory.addPreference(aboutWebsitePreference);
// RYD API connection statistics
if (BaseSettings.DEBUG.get()) {
PreferenceCategory emptyCategory = new PreferenceCategory(context); // vertical padding
preferenceScreen.addPreference(emptyCategory);
PreferenceCategory statisticsCategory = new PreferenceCategory(context);
statisticsCategory.setTitle(str("revanced_ryd_statistics_category_title"));
preferenceScreen.addPreference(statisticsCategory);
Preference statisticPreference;
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeAverage_title"));
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeAverage()));
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeMin_title"));
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMin()));
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeMax_title"));
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMax()));
preferenceScreen.addPreference(statisticPreference);
String fetchCallTimeWaitingLastSummary;
final long fetchCallTimeWaitingLast = ReturnYouTubeDislikeApi.getFetchCallResponseTimeLast();
if (fetchCallTimeWaitingLast == ReturnYouTubeDislikeApi.FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT) {
fetchCallTimeWaitingLastSummary = str("revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary");
} else {
fetchCallTimeWaitingLastSummary = createMillisecondStringFromNumber(fetchCallTimeWaitingLast);
}
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeLast_title"));
statisticPreference.setSummary(fetchCallTimeWaitingLastSummary);
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallCount_title"));
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getFetchCallCount(),
"revanced_ryd_statistics_getFetchCallCount_zero_summary",
"revanced_ryd_statistics_getFetchCallCount_non_zero_summary"));
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallNumberOfFailures_title"));
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getFetchCallNumberOfFailures(),
"revanced_ryd_statistics_getFetchCallNumberOfFailures_zero_summary",
"revanced_ryd_statistics_getFetchCallNumberOfFailures_non_zero_summary"));
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_title"));
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getNumberOfRateLimitRequestsEncountered(),
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_zero_summary",
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"));
preferenceScreen.addPreference(statisticPreference);
}
Utils.setPreferenceTitlesToMultiLineIfNeeded(preferenceScreen);
} catch (Exception ex) {
Logger.printException(() -> "onCreate failure", ex);
}
}
private static String createSummaryText(int value, String summaryStringZeroKey, String summaryStringOneOrMoreKey) {
if (value == 0) {
return str(summaryStringZeroKey);
}
return String.format(str(summaryStringOneOrMoreKey), value);
}
private static String createMillisecondStringFromNumber(long number) {
return String.format(str("revanced_ryd_statistics_millisecond_text"), number);
}
}

View File

@@ -1,629 +0,0 @@
package app.revanced.extension.youtube.settings.preference;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.preference.*;
import android.text.Html;
import android.text.InputType;
import android.util.TypedValue;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategoryListPreference;
import app.revanced.extension.youtube.sponsorblock.objects.UserStats;
import app.revanced.extension.youtube.sponsorblock.requests.SBRequester;
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController;
import static android.text.Html.fromHtml;
import static app.revanced.extension.shared.StringRef.str;
@SuppressWarnings("deprecation")
public class SponsorBlockPreferenceFragment extends PreferenceFragment {
private SwitchPreference sbEnabled;
private SwitchPreference addNewSegment;
private SwitchPreference votingEnabled;
private SwitchPreference autoHideSkipSegmentButton;
private SwitchPreference compactSkipButton;
private SwitchPreference squareLayout;
private SwitchPreference showSkipToast;
private SwitchPreference trackSkips;
private SwitchPreference showTimeWithoutSegments;
private SwitchPreference toastOnConnectionError;
private ResettableEditTextPreference newSegmentStep;
private ResettableEditTextPreference minSegmentDuration;
private EditTextPreference privateUserId;
private EditTextPreference importExport;
private Preference apiUrl;
private PreferenceCategory statsCategory;
private PreferenceCategory segmentCategory;
private void updateUI() {
try {
final boolean enabled = Settings.SB_ENABLED.get();
if (!enabled) {
SponsorBlockViewController.hideAll();
SegmentPlaybackController.setCurrentVideoId(null);
} else if (!Settings.SB_CREATE_NEW_SEGMENT.get()) {
SponsorBlockViewController.hideNewSegmentLayout();
}
// Voting and add new segment buttons automatically show/hide themselves.
SponsorBlockViewController.updateLayout();
sbEnabled.setChecked(enabled);
addNewSegment.setChecked(Settings.SB_CREATE_NEW_SEGMENT.get());
addNewSegment.setEnabled(enabled);
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
votingEnabled.setEnabled(enabled);
autoHideSkipSegmentButton.setEnabled(enabled);
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
compactSkipButton.setEnabled(enabled);
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
squareLayout.setEnabled(enabled);
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
showSkipToast.setEnabled(enabled);
toastOnConnectionError.setChecked(Settings.SB_TOAST_ON_CONNECTION_ERROR.get());
toastOnConnectionError.setEnabled(enabled);
trackSkips.setChecked(Settings.SB_TRACK_SKIP_COUNT.get());
trackSkips.setEnabled(enabled);
showTimeWithoutSegments.setChecked(Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get());
showTimeWithoutSegments.setEnabled(enabled);
newSegmentStep.setText((Settings.SB_CREATE_NEW_SEGMENT_STEP.get()).toString());
newSegmentStep.setEnabled(enabled);
minSegmentDuration.setText((Settings.SB_SEGMENT_MIN_DURATION.get()).toString());
minSegmentDuration.setEnabled(enabled);
privateUserId.setText(Settings.SB_PRIVATE_USER_ID.get());
privateUserId.setEnabled(enabled);
// If the user has a private user id, then include a subtext that mentions not to share it.
String importExportSummary = SponsorBlockSettings.userHasSBPrivateId()
? str("revanced_sb_settings_ie_sum_warning")
: str("revanced_sb_settings_ie_sum");
importExport.setSummary(importExportSummary);
apiUrl.setEnabled(enabled);
importExport.setEnabled(enabled);
segmentCategory.setEnabled(enabled);
statsCategory.setEnabled(enabled);
} catch (Exception ex) {
Logger.printException(() -> "update settings UI failure", ex);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
Activity context = getActivity();
PreferenceManager manager = getPreferenceManager();
manager.setSharedPreferencesName(Setting.preferences.name);
PreferenceScreen preferenceScreen = manager.createPreferenceScreen(context);
setPreferenceScreen(preferenceScreen);
SponsorBlockSettings.initialize();
sbEnabled = new SwitchPreference(context);
sbEnabled.setTitle(str("revanced_sb_enable_sb"));
sbEnabled.setSummary(str("revanced_sb_enable_sb_sum"));
preferenceScreen.addPreference(sbEnabled);
sbEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_ENABLED.save((Boolean) newValue);
updateUI();
return true;
});
addAppearanceCategory(context, preferenceScreen);
segmentCategory = new PreferenceCategory(context);
segmentCategory.setTitle(str("revanced_sb_diff_segments"));
preferenceScreen.addPreference(segmentCategory);
updateSegmentCategories();
addCreateSegmentCategory(context, preferenceScreen);
addGeneralCategory(context, preferenceScreen);
statsCategory = new PreferenceCategory(context);
statsCategory.setTitle(str("revanced_sb_stats"));
preferenceScreen.addPreference(statsCategory);
fetchAndDisplayStats();
addAboutCategory(context, preferenceScreen);
Utils.setPreferenceTitlesToMultiLineIfNeeded(preferenceScreen);
updateUI();
} catch (Exception ex) {
Logger.printException(() -> "onCreate failure", ex);
}
}
private void addAppearanceCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle(str("revanced_sb_appearance_category"));
votingEnabled = new SwitchPreference(context);
votingEnabled.setTitle(str("revanced_sb_enable_voting"));
votingEnabled.setSummaryOn(str("revanced_sb_enable_voting_sum_on"));
votingEnabled.setSummaryOff(str("revanced_sb_enable_voting_sum_off"));
category.addPreference(votingEnabled);
votingEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_VOTING_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
autoHideSkipSegmentButton = new SwitchPreference(context);
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
category.addPreference(autoHideSkipSegmentButton);
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
compactSkipButton = new SwitchPreference(context);
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
compactSkipButton.setSummaryOff(str("revanced_sb_enable_compact_skip_button_sum_off"));
category.addPreference(compactSkipButton);
compactSkipButton.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_COMPACT_SKIP_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
squareLayout = new SwitchPreference(context);
squareLayout.setTitle(str("revanced_sb_square_layout"));
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
category.addPreference(squareLayout);
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
updateUI();
return true;
});
showSkipToast = new SwitchPreference(context);
showSkipToast.setTitle(str("revanced_sb_general_skiptoast"));
showSkipToast.setSummaryOn(str("revanced_sb_general_skiptoast_sum_on"));
showSkipToast.setSummaryOff(str("revanced_sb_general_skiptoast_sum_off"));
showSkipToast.setOnPreferenceClickListener(preference1 -> {
Utils.showToastShort(str("revanced_sb_skipped_sponsor"));
return false;
});
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_TOAST_ON_SKIP.save((Boolean) newValue);
updateUI();
return true;
});
category.addPreference(showSkipToast);
showTimeWithoutSegments = new SwitchPreference(context);
showTimeWithoutSegments.setTitle(str("revanced_sb_general_time_without"));
showTimeWithoutSegments.setSummaryOn(str("revanced_sb_general_time_without_sum_on"));
showTimeWithoutSegments.setSummaryOff(str("revanced_sb_general_time_without_sum_off"));
showTimeWithoutSegments.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.save((Boolean) newValue);
updateUI();
return true;
});
category.addPreference(showTimeWithoutSegments);
}
private void addCreateSegmentCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle(str("revanced_sb_create_segment_category"));
addNewSegment = new SwitchPreference(context);
addNewSegment.setTitle(str("revanced_sb_enable_create_segment"));
addNewSegment.setSummaryOn(str("revanced_sb_enable_create_segment_sum_on"));
addNewSegment.setSummaryOff(str("revanced_sb_enable_create_segment_sum_off"));
category.addPreference(addNewSegment);
addNewSegment.setOnPreferenceChangeListener((preference1, o) -> {
Boolean newValue = (Boolean) o;
if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) {
new AlertDialog.Builder(preference1.getContext())
.setTitle(str("revanced_sb_guidelines_popup_title"))
.setMessage(str("revanced_sb_guidelines_popup_content"))
.setNegativeButton(str("revanced_sb_guidelines_popup_already_read"), null)
.setPositiveButton(str("revanced_sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines())
.setOnDismissListener(dialog -> Settings.SB_SEEN_GUIDELINES.save(true))
.setCancelable(false)
.show();
}
Settings.SB_CREATE_NEW_SEGMENT.save(newValue);
updateUI();
return true;
});
newSegmentStep = new ResettableEditTextPreference(context);
newSegmentStep.setSetting(Settings.SB_CREATE_NEW_SEGMENT_STEP);
newSegmentStep.setTitle(str("revanced_sb_general_adjusting"));
newSegmentStep.setSummary(str("revanced_sb_general_adjusting_sum"));
newSegmentStep.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
newSegmentStep.setOnPreferenceChangeListener((preference1, newValue) -> {
try {
final int newAdjustmentValue = Integer.parseInt(newValue.toString());
if (newAdjustmentValue != 0) {
Settings.SB_CREATE_NEW_SEGMENT_STEP.save(newAdjustmentValue);
return true;
}
} catch (NumberFormatException ex) {
Logger.printInfo(() -> "Invalid new segment step", ex);
}
Utils.showToastLong(str("revanced_sb_general_adjusting_invalid"));
updateUI();
return false;
});
category.addPreference(newSegmentStep);
Preference guidelinePreferences = new Preference(context);
guidelinePreferences.setTitle(str("revanced_sb_guidelines_preference_title"));
guidelinePreferences.setSummary(str("revanced_sb_guidelines_preference_sum"));
guidelinePreferences.setOnPreferenceClickListener(preference1 -> {
openGuidelines();
return true;
});
category.addPreference(guidelinePreferences);
}
private void addGeneralCategory(final Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle(str("revanced_sb_general"));
toastOnConnectionError = new SwitchPreference(context);
toastOnConnectionError.setTitle(str("revanced_sb_toast_on_connection_error_title"));
toastOnConnectionError.setSummaryOn(str("revanced_sb_toast_on_connection_error_summary_on"));
toastOnConnectionError.setSummaryOff(str("revanced_sb_toast_on_connection_error_summary_off"));
toastOnConnectionError.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_TOAST_ON_CONNECTION_ERROR.save((Boolean) newValue);
updateUI();
return true;
});
category.addPreference(toastOnConnectionError);
trackSkips = new SwitchPreference(context);
trackSkips.setTitle(str("revanced_sb_general_skipcount"));
trackSkips.setSummaryOn(str("revanced_sb_general_skipcount_sum_on"));
trackSkips.setSummaryOff(str("revanced_sb_general_skipcount_sum_off"));
trackSkips.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_TRACK_SKIP_COUNT.save((Boolean) newValue);
updateUI();
return true;
});
category.addPreference(trackSkips);
minSegmentDuration = new ResettableEditTextPreference(context);
minSegmentDuration.setSetting(Settings.SB_SEGMENT_MIN_DURATION);
minSegmentDuration.setTitle(str("revanced_sb_general_min_duration"));
minSegmentDuration.setSummary(str("revanced_sb_general_min_duration_sum"));
minSegmentDuration.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
minSegmentDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
try {
Float minTimeDuration = Float.valueOf(newValue.toString());
Settings.SB_SEGMENT_MIN_DURATION.save(minTimeDuration);
return true;
} catch (NumberFormatException ex) {
Logger.printInfo(() -> "Invalid minimum segment duration", ex);
}
Utils.showToastLong(str("revanced_sb_general_min_duration_invalid"));
updateUI();
return false;
});
category.addPreference(minSegmentDuration);
privateUserId = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString());
});
}
};
privateUserId.setTitle(str("revanced_sb_general_uuid"));
privateUserId.setSummary(str("revanced_sb_general_uuid_sum"));
privateUserId.setOnPreferenceChangeListener((preference1, newValue) -> {
String newUUID = newValue.toString();
if (!SponsorBlockSettings.isValidSBUserId(newUUID)) {
Utils.showToastLong(str("revanced_sb_general_uuid_invalid"));
return false;
}
Settings.SB_PRIVATE_USER_ID.save(newUUID);
updateUI();
fetchAndDisplayStats();
return true;
});
category.addPreference(privateUserId);
apiUrl = new Preference(context);
apiUrl.setTitle(str("revanced_sb_general_api_url"));
apiUrl.setSummary(Html.fromHtml(str("revanced_sb_general_api_url_sum")));
apiUrl.setOnPreferenceClickListener(preference1 -> {
EditText editText = new EditText(context);
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
editText.setText(Settings.SB_API_URL.get());
DialogInterface.OnClickListener urlChangeListener = (dialog, buttonPressed) -> {
if (buttonPressed == DialogInterface.BUTTON_NEUTRAL) {
Settings.SB_API_URL.resetToDefault();
Utils.showToastLong(str("revanced_sb_api_url_reset"));
} else if (buttonPressed == DialogInterface.BUTTON_POSITIVE) {
String serverAddress = editText.getText().toString();
if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) {
Utils.showToastLong(str("revanced_sb_api_url_invalid"));
} else if (!serverAddress.equals(Settings.SB_API_URL.get())) {
Settings.SB_API_URL.save(serverAddress);
Utils.showToastLong(str("revanced_sb_api_url_changed"));
}
}
};
new AlertDialog.Builder(context)
.setTitle(apiUrl.getTitle())
.setView(editText)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(str("revanced_sb_reset"), urlChangeListener)
.setPositiveButton(android.R.string.ok, urlChangeListener)
.show();
return true;
});
category.addPreference(apiUrl);
importExport = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString());
});
}
};
importExport.setTitle(str("revanced_sb_settings_ie"));
// Summary is set in updateUI()
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
importExport.getEditText().setAutofillHints((String) null);
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
importExport.setOnPreferenceClickListener(preference1 -> {
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());
return true;
});
importExport.setOnPreferenceChangeListener((preference1, newValue) -> {
SponsorBlockSettings.importDesktopSettings((String) newValue);
updateSegmentCategories();
fetchAndDisplayStats();
updateUI();
return true;
});
category.addPreference(importExport);
}
private void updateSegmentCategories() {
try {
segmentCategory.removeAll();
Activity activity = getActivity();
for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
segmentCategory.addPreference(new SegmentCategoryListPreference(activity, category));
}
} catch (Exception ex) {
Logger.printException(() -> "updateSegmentCategories failure", ex);
}
}
private void addAboutCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle(str("revanced_sb_about"));
{
Preference preference = new Preference(context);
category.addPreference(preference);
preference.setTitle(str("revanced_sb_about_api"));
preference.setSummary(str("revanced_sb_about_api_sum"));
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sponsor.ajay.app"));
preference1.getContext().startActivity(i);
return false;
});
}
}
private void openGuidelines() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://wiki.sponsor.ajay.app/w/Guidelines"));
getActivity().startActivity(intent);
}
private void fetchAndDisplayStats() {
try {
statsCategory.removeAll();
if (!SponsorBlockSettings.userHasSBPrivateId()) {
// User has never voted or created any segments. No stats to show.
addLocalUserStats();
return;
}
Preference loadingPlaceholderPreference = new Preference(this.getActivity());
loadingPlaceholderPreference.setEnabled(false);
statsCategory.addPreference(loadingPlaceholderPreference);
if (Settings.SB_ENABLED.get()) {
loadingPlaceholderPreference.setTitle(str("revanced_sb_stats_loading"));
Utils.runOnBackgroundThread(() -> {
UserStats stats = SBRequester.retrieveUserStats();
Utils.runOnMainThread(() -> { // get back on main thread to modify UI elements
addUserStats(loadingPlaceholderPreference, stats);
addLocalUserStats();
});
});
} else {
loadingPlaceholderPreference.setTitle(str("revanced_sb_stats_sb_disabled"));
}
} catch (Exception ex) {
Logger.printException(() -> "fetchAndDisplayStats failure", ex);
}
}
private void addUserStats(@NonNull Preference loadingPlaceholder, @Nullable UserStats stats) {
Utils.verifyOnMainThread();
try {
if (stats == null) {
loadingPlaceholder.setTitle(str("revanced_sb_stats_connection_failure"));
return;
}
statsCategory.removeAll();
Context context = statsCategory.getContext();
if (stats.totalSegmentCountIncludingIgnored > 0) {
// If user has not created any segments, there's no reason to set a username.
EditTextPreference preference = new ResettableEditTextPreference(context);
statsCategory.addPreference(preference);
String userName = stats.userName;
preference.setTitle(fromHtml(str("revanced_sb_stats_username", userName)));
preference.setSummary(str("revanced_sb_stats_username_change"));
preference.setText(userName);
preference.setOnPreferenceChangeListener((preference1, value) -> {
Utils.runOnBackgroundThread(() -> {
String newUserName = (String) value;
String errorMessage = SBRequester.setUsername(newUserName);
Utils.runOnMainThread(() -> {
if (errorMessage == null) {
preference.setTitle(fromHtml(str("revanced_sb_stats_username", newUserName)));
preference.setText(newUserName);
Utils.showToastLong(str("revanced_sb_stats_username_changed"));
} else {
preference.setText(userName); // revert to previous
SponsorBlockUtils.showErrorDialog(errorMessage);
}
});
});
return true;
});
}
{
// number of segment submissions (does not include ignored segments)
Preference preference = new Preference(context);
statsCategory.addPreference(preference);
String formatted = SponsorBlockUtils.getNumberOfSkipsString(stats.segmentCount);
preference.setTitle(fromHtml(str("revanced_sb_stats_submissions", formatted)));
preference.setSummary(str("revanced_sb_stats_submissions_sum"));
if (stats.totalSegmentCountIncludingIgnored == 0) {
preference.setSelectable(false);
} else {
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sb.ltn.fi/userid/" + stats.publicUserId));
preference1.getContext().startActivity(i);
return true;
});
}
}
{
// "user reputation". Usually not useful, since it appears most users have zero reputation.
// But if there is a reputation, then show it here
Preference preference = new Preference(context);
preference.setTitle(fromHtml(str("revanced_sb_stats_reputation", stats.reputation)));
preference.setSelectable(false);
if (stats.reputation != 0) {
statsCategory.addPreference(preference);
}
}
{
// time saved for other users
Preference preference = new Preference(context);
statsCategory.addPreference(preference);
String stats_saved;
String stats_saved_sum;
if (stats.totalSegmentCountIncludingIgnored == 0) {
stats_saved = str("revanced_sb_stats_saved_zero");
stats_saved_sum = str("revanced_sb_stats_saved_sum_zero");
} else {
stats_saved = str("revanced_sb_stats_saved",
SponsorBlockUtils.getNumberOfSkipsString(stats.viewCount));
stats_saved_sum = str("revanced_sb_stats_saved_sum", SponsorBlockUtils.getTimeSavedString((long) (60 * stats.minutesSaved)));
}
preference.setTitle(fromHtml(stats_saved));
preference.setSummary(fromHtml(stats_saved_sum));
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sponsor.ajay.app/stats/"));
preference1.getContext().startActivity(i);
return false;
});
}
} catch (Exception ex) {
Logger.printException(() -> "addUserStats failure", ex);
}
}
private void addLocalUserStats() {
// time the user saved by using SB
Preference preference = new Preference(statsCategory.getContext());
statsCategory.addPreference(preference);
Runnable updateStatsSelfSaved = () -> {
String formatted = SponsorBlockUtils.getNumberOfSkipsString(Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.get());
preference.setTitle(fromHtml(str("revanced_sb_stats_self_saved", formatted)));
String formattedSaved = SponsorBlockUtils.getTimeSavedString(Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.get() / 1000);
preference.setSummary(fromHtml(str("revanced_sb_stats_self_saved_sum", formattedSaved)));
};
updateStatsSelfSaved.run();
preference.setOnPreferenceClickListener(preference1 -> {
new AlertDialog.Builder(preference1.getContext())
.setTitle(str("revanced_sb_stats_self_saved_reset_title"))
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault();
Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault();
updateStatsSelfSaved.run();
})
.setNegativeButton(android.R.string.no, null).show();
return true;
});
}
}

View File

@@ -0,0 +1,44 @@
package app.revanced.extension.youtube.settings.preference;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.preference.Preference;
import android.util.AttributeSet;
import app.revanced.extension.shared.Logger;
/**
* Simple preference that opens a url when clicked.
*/
@SuppressWarnings("deprecation")
public class UrlLinkPreference extends Preference {
protected String externalUrl;
{
setOnPreferenceClickListener(pref -> {
if (externalUrl == null) {
Logger.printException(() -> "URL not set " + getClass().getSimpleName());
return false;
}
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(externalUrl));
pref.getContext().startActivity(i);
return true;
});
}
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public UrlLinkPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public UrlLinkPreference(Context context) {
super(context);
}
}

View File

@@ -18,6 +18,7 @@ import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
import app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour;
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
@@ -31,6 +32,7 @@ public class SponsorBlockSettings {
@Override
public void settingsImported(@Nullable Context context) {
SegmentCategory.loadAllCategoriesFromSettings();
SponsorBlockPreferenceGroup.settingsImported = true;
}
@Override
public void settingsExported(@Nullable Context context) {

View File

@@ -55,7 +55,7 @@ public class SegmentCategoryListPreference extends ListPreference {
: CategoryBehaviour.getBehaviorKeyValues());
setSummary(category.description.toString());
updateTitleFromCategory();
updateUI();
}
@Override
@@ -202,7 +202,7 @@ public class SegmentCategoryListPreference extends ListPreference {
builder.setNeutralButton(str("revanced_sb_reset_color"), (dialog, which) -> {
try {
category.resetColorAndOpacity();
updateTitleFromCategory();
updateUI();
Utils.showToastShort(str("revanced_sb_color_reset"));
} catch (Exception ex) {
Logger.printException(() -> "setNeutralButton failure", ex);
@@ -240,7 +240,7 @@ public class SegmentCategoryListPreference extends ListPreference {
Utils.showToastShort(str("revanced_sb_color_invalid"));
}
updateTitleFromCategory();
updateUI();
}
} catch (Exception ex) {
Logger.printException(() -> "onDialogClosed failure", ex);
@@ -251,7 +251,7 @@ public class SegmentCategoryListPreference extends ListPreference {
categoryColor = applyOpacityToColor(categoryColor, categoryOpacity);
}
private void updateTitleFromCategory() {
public void updateUI() {
categoryColor = category.getColorNoOpacity();
categoryOpacity = category.getOpacity();
applyOpacityToCategoryColor();

View File

@@ -5,13 +5,19 @@ import androidx.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
/**
* SponsorBlock user stats
*/
public class UserStats {
@NonNull
/**
* How long to cache user stats objects.
*/
private static final long STATS_EXPIRATION_MILLISECONDS = 60 * 60 * 1000; // 60 minutes.
private final String privateUserId;
public final String publicUserId;
@NonNull
public final String userName;
/**
* "User reputation". Unclear how SB determines this value.
@@ -26,7 +32,13 @@ public class UserStats {
public final int viewCount;
public final double minutesSaved;
public UserStats(@NonNull JSONObject json) throws JSONException {
/**
* When this stat was fetched.
*/
public final long fetchTime;
public UserStats(String privateSbId, @NonNull JSONObject json) throws JSONException {
privateUserId = privateSbId;
publicUserId = json.getString("userID");
userName = json.getString("userName");
reputation = (float)json.getDouble("reputation");
@@ -35,11 +47,23 @@ public class UserStats {
totalSegmentCountIncludingIgnored = segmentCount + ignoredSegmentCount;
viewCount = json.getInt("viewCount");
minutesSaved = json.getDouble("minutesSaved");
fetchTime = System.currentTimeMillis();
}
public boolean isExpired() {
if (STATS_EXPIRATION_MILLISECONDS < System.currentTimeMillis() - fetchTime) {
return true;
}
// User changed their SB private user id.
return !SponsorBlockSettings.userHasSBPrivateId()
|| !SponsorBlockSettings.getSBPrivateUserID().equals(privateUserId);
}
@NonNull
@Override
public String toString() {
// Do not include private user id in toString().
return "UserStats{"
+ "publicUserId='" + publicUserId + '\''
+ ", userName='" + userName + '\''

View File

@@ -47,6 +47,9 @@ public class SBRequester {
*/
private static final int HTTP_STATUS_CODE_SUCCESS = 200;
@Nullable
private static volatile UserStats lastFetchedStats;
private SBRequester() {
}
@@ -181,6 +184,8 @@ public class SBRequester {
Utils.showToastLong(str("revanced_sb_submit_failed_unknown_error", 0, ex.getMessage()));
} catch (Exception ex) {
Logger.printException(() -> "failed to submit segments", ex); // Should never happen.
} finally {
lastFetchedStats = null; // Fetch updated stats if needed.
}
}
@@ -252,9 +257,17 @@ public class SBRequester {
public static UserStats retrieveUserStats() {
Utils.verifyOffMainThread();
try {
UserStats stats = new UserStats(getJSONObject(SBRoutes.GET_USER_STATS, SponsorBlockSettings.getSBPrivateUserID()));
Logger.printDebug(() -> "user stats: " + stats);
return stats;
UserStats stats = lastFetchedStats;
if (stats != null && !stats.isExpired()) {
return stats;
}
String privateUserID = SponsorBlockSettings.getSBPrivateUserID();
UserStats fetchedStats = new UserStats(privateUserID,
getJSONObject(SBRoutes.GET_USER_STATS, privateUserID));
Logger.printDebug(() -> "user stats: " + fetchedStats);
lastFetchedStats = fetchedStats;
return fetchedStats;
} catch (IOException ex) {
Logger.printInfo(() -> "failed to retrieve user stats", ex); // info level, do not show a toast
} catch (Exception ex) {

View File

@@ -0,0 +1,26 @@
package app.revanced.extension.youtube.sponsorblock.ui;
import android.content.Context;
import android.util.AttributeSet;
import app.revanced.extension.youtube.settings.preference.UrlLinkPreference;
@SuppressWarnings("unused")
public class SponsorBlockAboutPreference extends UrlLinkPreference {
{
externalUrl = "https://sponsor.ajay.app";
}
public SponsorBlockAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public SponsorBlockAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SponsorBlockAboutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SponsorBlockAboutPreference(Context context) {
super(context);
}
}

View File

@@ -0,0 +1,471 @@
package app.revanced.extension.youtube.sponsorblock.ui;
import static app.revanced.extension.shared.StringRef.str;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.preference.*;
import android.text.Html;
import android.text.InputType;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import java.util.ArrayList;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategoryListPreference;
/**
* Lots of old code that could be converted to a half dozen custom preferences,
* but instead it's wrapped in this group container and all logic is handled here.
*/
@SuppressWarnings({"unused", "deprecation"})
public class SponsorBlockPreferenceGroup extends PreferenceGroup {
/**
* ReVanced settings were recently imported and the UI needs to be updated.
*/
public static boolean settingsImported;
/**
* If the preferences have been created and added to this group.
*/
private boolean preferencesInitialized;
private SwitchPreference sbEnabled;
private SwitchPreference addNewSegment;
private SwitchPreference votingEnabled;
private SwitchPreference autoHideSkipSegmentButton;
private SwitchPreference compactSkipButton;
private SwitchPreference squareLayout;
private SwitchPreference showSkipToast;
private SwitchPreference trackSkips;
private SwitchPreference showTimeWithoutSegments;
private SwitchPreference toastOnConnectionError;
private ResettableEditTextPreference newSegmentStep;
private ResettableEditTextPreference minSegmentDuration;
private EditTextPreference privateUserId;
private EditTextPreference importExport;
private Preference apiUrl;
private final List<SegmentCategoryListPreference> segmentCategories = new ArrayList<>();
private PreferenceCategory segmentCategory;
public SponsorBlockPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public SponsorBlockPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SponsorBlockPreferenceGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
@SuppressLint("MissingSuperCall")
protected View onCreateView(ViewGroup parent) {
// Title is not shown.
return new View(getContext());
}
private void updateUI() {
try {
Logger.printDebug(() -> "updateUI");
final boolean enabled = Settings.SB_ENABLED.get();
if (!enabled) {
SponsorBlockViewController.hideAll();
SegmentPlaybackController.setCurrentVideoId(null);
} else if (!Settings.SB_CREATE_NEW_SEGMENT.get()) {
SponsorBlockViewController.hideNewSegmentLayout();
}
// Voting and add new segment buttons automatically show/hide themselves.
SponsorBlockViewController.updateLayout();
sbEnabled.setChecked(enabled);
addNewSegment.setChecked(Settings.SB_CREATE_NEW_SEGMENT.get());
addNewSegment.setEnabled(enabled);
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
votingEnabled.setEnabled(enabled);
autoHideSkipSegmentButton.setEnabled(enabled);
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
compactSkipButton.setEnabled(enabled);
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
squareLayout.setEnabled(enabled);
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
showSkipToast.setEnabled(enabled);
toastOnConnectionError.setChecked(Settings.SB_TOAST_ON_CONNECTION_ERROR.get());
toastOnConnectionError.setEnabled(enabled);
trackSkips.setChecked(Settings.SB_TRACK_SKIP_COUNT.get());
trackSkips.setEnabled(enabled);
showTimeWithoutSegments.setChecked(Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get());
showTimeWithoutSegments.setEnabled(enabled);
newSegmentStep.setText((Settings.SB_CREATE_NEW_SEGMENT_STEP.get()).toString());
newSegmentStep.setEnabled(enabled);
minSegmentDuration.setText((Settings.SB_SEGMENT_MIN_DURATION.get()).toString());
minSegmentDuration.setEnabled(enabled);
privateUserId.setText(Settings.SB_PRIVATE_USER_ID.get());
privateUserId.setEnabled(enabled);
// If the user has a private user id, then include a subtext that mentions not to share it.
String importExportSummary = SponsorBlockSettings.userHasSBPrivateId()
? str("revanced_sb_settings_ie_sum_warning")
: str("revanced_sb_settings_ie_sum");
importExport.setSummary(importExportSummary);
apiUrl.setEnabled(enabled);
importExport.setEnabled(enabled);
segmentCategory.setEnabled(enabled);
for (SegmentCategoryListPreference category : segmentCategories) {
category.updateUI();
}
} catch (Exception ex) {
Logger.printException(() -> "updateUI failure", ex);
}
}
protected void onAttachedToActivity() {
try {
super.onAttachedToActivity();
if (preferencesInitialized) {
if (settingsImported) {
settingsImported = false;
updateUI();
}
return;
}
preferencesInitialized = true;
Logger.printDebug(() -> "Creating settings preferences");
Context context = getContext();
SponsorBlockSettings.initialize();
sbEnabled = new SwitchPreference(context);
sbEnabled.setTitle(str("revanced_sb_enable_sb"));
sbEnabled.setSummary(str("revanced_sb_enable_sb_sum"));
addPreference(sbEnabled);
sbEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_ENABLED.save((Boolean) newValue);
updateUI();
return true;
});
PreferenceCategory appearanceCategory = new PreferenceCategory(context);
appearanceCategory.setTitle(str("revanced_sb_appearance_category"));
addPreference(appearanceCategory);
votingEnabled = new SwitchPreference(context);
votingEnabled.setTitle(str("revanced_sb_enable_voting"));
votingEnabled.setSummaryOn(str("revanced_sb_enable_voting_sum_on"));
votingEnabled.setSummaryOff(str("revanced_sb_enable_voting_sum_off"));
votingEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_VOTING_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
appearanceCategory.addPreference(votingEnabled);
autoHideSkipSegmentButton = new SwitchPreference(context);
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
appearanceCategory.addPreference(autoHideSkipSegmentButton);
compactSkipButton = new SwitchPreference(context);
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
compactSkipButton.setSummaryOff(str("revanced_sb_enable_compact_skip_button_sum_off"));
compactSkipButton.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_COMPACT_SKIP_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
appearanceCategory.addPreference(compactSkipButton);
squareLayout = new SwitchPreference(context);
squareLayout.setTitle(str("revanced_sb_square_layout"));
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
updateUI();
return true;
});
appearanceCategory.addPreference(squareLayout);
showSkipToast = new SwitchPreference(context);
showSkipToast.setTitle(str("revanced_sb_general_skiptoast"));
showSkipToast.setSummaryOn(str("revanced_sb_general_skiptoast_sum_on"));
showSkipToast.setSummaryOff(str("revanced_sb_general_skiptoast_sum_off"));
showSkipToast.setOnPreferenceClickListener(preference1 -> {
Utils.showToastShort(str("revanced_sb_skipped_sponsor"));
return false;
});
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_TOAST_ON_SKIP.save((Boolean) newValue);
updateUI();
return true;
});
appearanceCategory.addPreference(showSkipToast);
showTimeWithoutSegments = new SwitchPreference(context);
showTimeWithoutSegments.setTitle(str("revanced_sb_general_time_without"));
showTimeWithoutSegments.setSummaryOn(str("revanced_sb_general_time_without_sum_on"));
showTimeWithoutSegments.setSummaryOff(str("revanced_sb_general_time_without_sum_off"));
showTimeWithoutSegments.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.save((Boolean) newValue);
updateUI();
return true;
});
appearanceCategory.addPreference(showTimeWithoutSegments);
segmentCategory = new PreferenceCategory(context);
segmentCategory.setTitle(str("revanced_sb_diff_segments"));
addPreference(segmentCategory);
for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
SegmentCategoryListPreference categoryPreference = new SegmentCategoryListPreference(context, category);
segmentCategories.add(categoryPreference);
segmentCategory.addPreference(categoryPreference);
}
PreferenceCategory createSegmentCategory = new PreferenceCategory(context);
createSegmentCategory.setTitle(str("revanced_sb_create_segment_category"));
addPreference(createSegmentCategory);
addNewSegment = new SwitchPreference(context);
addNewSegment.setTitle(str("revanced_sb_enable_create_segment"));
addNewSegment.setSummaryOn(str("revanced_sb_enable_create_segment_sum_on"));
addNewSegment.setSummaryOff(str("revanced_sb_enable_create_segment_sum_off"));
addNewSegment.setOnPreferenceChangeListener((preference1, o) -> {
Boolean newValue = (Boolean) o;
if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) {
new AlertDialog.Builder(preference1.getContext())
.setTitle(str("revanced_sb_guidelines_popup_title"))
.setMessage(str("revanced_sb_guidelines_popup_content"))
.setNegativeButton(str("revanced_sb_guidelines_popup_already_read"), null)
.setPositiveButton(str("revanced_sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines())
.setOnDismissListener(dialog -> Settings.SB_SEEN_GUIDELINES.save(true))
.setCancelable(false)
.show();
}
Settings.SB_CREATE_NEW_SEGMENT.save(newValue);
updateUI();
return true;
});
createSegmentCategory.addPreference(addNewSegment);
newSegmentStep = new ResettableEditTextPreference(context);
newSegmentStep.setSetting(Settings.SB_CREATE_NEW_SEGMENT_STEP);
newSegmentStep.setTitle(str("revanced_sb_general_adjusting"));
newSegmentStep.setSummary(str("revanced_sb_general_adjusting_sum"));
newSegmentStep.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
newSegmentStep.setOnPreferenceChangeListener((preference1, newValue) -> {
try {
final int newAdjustmentValue = Integer.parseInt(newValue.toString());
if (newAdjustmentValue != 0) {
Settings.SB_CREATE_NEW_SEGMENT_STEP.save(newAdjustmentValue);
return true;
}
} catch (NumberFormatException ex) {
Logger.printInfo(() -> "Invalid new segment step", ex);
}
Utils.showToastLong(str("revanced_sb_general_adjusting_invalid"));
updateUI();
return false;
});
createSegmentCategory.addPreference(newSegmentStep);
Preference guidelinePreferences = new Preference(context);
guidelinePreferences.setTitle(str("revanced_sb_guidelines_preference_title"));
guidelinePreferences.setSummary(str("revanced_sb_guidelines_preference_sum"));
guidelinePreferences.setOnPreferenceClickListener(preference1 -> {
openGuidelines();
return true;
});
createSegmentCategory.addPreference(guidelinePreferences);
PreferenceCategory generalCategory = new PreferenceCategory(context);
generalCategory.setTitle(str("revanced_sb_general"));
addPreference(generalCategory);
toastOnConnectionError = new SwitchPreference(context);
toastOnConnectionError.setTitle(str("revanced_sb_toast_on_connection_error_title"));
toastOnConnectionError.setSummaryOn(str("revanced_sb_toast_on_connection_error_summary_on"));
toastOnConnectionError.setSummaryOff(str("revanced_sb_toast_on_connection_error_summary_off"));
toastOnConnectionError.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_TOAST_ON_CONNECTION_ERROR.save((Boolean) newValue);
updateUI();
return true;
});
generalCategory.addPreference(toastOnConnectionError);
trackSkips = new SwitchPreference(context);
trackSkips.setTitle(str("revanced_sb_general_skipcount"));
trackSkips.setSummaryOn(str("revanced_sb_general_skipcount_sum_on"));
trackSkips.setSummaryOff(str("revanced_sb_general_skipcount_sum_off"));
trackSkips.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_TRACK_SKIP_COUNT.save((Boolean) newValue);
updateUI();
return true;
});
generalCategory.addPreference(trackSkips);
minSegmentDuration = new ResettableEditTextPreference(context);
minSegmentDuration.setSetting(Settings.SB_SEGMENT_MIN_DURATION);
minSegmentDuration.setTitle(str("revanced_sb_general_min_duration"));
minSegmentDuration.setSummary(str("revanced_sb_general_min_duration_sum"));
minSegmentDuration.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
minSegmentDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
try {
Float minTimeDuration = Float.valueOf(newValue.toString());
Settings.SB_SEGMENT_MIN_DURATION.save(minTimeDuration);
return true;
} catch (NumberFormatException ex) {
Logger.printInfo(() -> "Invalid minimum segment duration", ex);
}
Utils.showToastLong(str("revanced_sb_general_min_duration_invalid"));
updateUI();
return false;
});
generalCategory.addPreference(minSegmentDuration);
privateUserId = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString());
});
}
};
privateUserId.setTitle(str("revanced_sb_general_uuid"));
privateUserId.setSummary(str("revanced_sb_general_uuid_sum"));
privateUserId.setOnPreferenceChangeListener((preference1, newValue) -> {
String newUUID = newValue.toString();
if (!SponsorBlockSettings.isValidSBUserId(newUUID)) {
Utils.showToastLong(str("revanced_sb_general_uuid_invalid"));
return false;
}
Settings.SB_PRIVATE_USER_ID.save(newUUID);
updateUI();
return true;
});
generalCategory.addPreference(privateUserId);
apiUrl = new Preference(context);
apiUrl.setTitle(str("revanced_sb_general_api_url"));
apiUrl.setSummary(Html.fromHtml(str("revanced_sb_general_api_url_sum")));
apiUrl.setOnPreferenceClickListener(preference1 -> {
EditText editText = new EditText(context);
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
editText.setText(Settings.SB_API_URL.get());
DialogInterface.OnClickListener urlChangeListener = (dialog, buttonPressed) -> {
if (buttonPressed == DialogInterface.BUTTON_NEUTRAL) {
Settings.SB_API_URL.resetToDefault();
Utils.showToastLong(str("revanced_sb_api_url_reset"));
} else if (buttonPressed == DialogInterface.BUTTON_POSITIVE) {
String serverAddress = editText.getText().toString();
if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) {
Utils.showToastLong(str("revanced_sb_api_url_invalid"));
} else if (!serverAddress.equals(Settings.SB_API_URL.get())) {
Settings.SB_API_URL.save(serverAddress);
Utils.showToastLong(str("revanced_sb_api_url_changed"));
}
}
};
new AlertDialog.Builder(context)
.setTitle(apiUrl.getTitle())
.setView(editText)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(str("revanced_sb_reset"), urlChangeListener)
.setPositiveButton(android.R.string.ok, urlChangeListener)
.show();
return true;
});
generalCategory.addPreference(apiUrl);
importExport = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString());
});
}
};
importExport.setTitle(str("revanced_sb_settings_ie"));
// Summary is set in updateUI()
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
importExport.getEditText().setAutofillHints((String) null);
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
importExport.setOnPreferenceClickListener(preference1 -> {
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());
return true;
});
importExport.setOnPreferenceChangeListener((preference1, newValue) -> {
SponsorBlockSettings.importDesktopSettings((String) newValue);
updateUI();
return true;
});
generalCategory.addPreference(importExport);
Utils.setPreferenceTitlesToMultiLineIfNeeded(this);
updateUI();
} catch (Exception ex) {
Logger.printException(() -> "onAttachedToActivity failure", ex);
}
}
private void openGuidelines() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://wiki.sponsor.ajay.app/w/Guidelines"));
getContext().startActivity(intent);
}
}

View File

@@ -0,0 +1,210 @@
package app.revanced.extension.youtube.sponsorblock.ui;
import static android.text.Html.fromHtml;
import static app.revanced.extension.shared.StringRef.str;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
import app.revanced.extension.youtube.sponsorblock.objects.UserStats;
import app.revanced.extension.youtube.sponsorblock.requests.SBRequester;
/**
* User skip stats.
*
* None of the preferences here show up in search results because
* a category cannot be added to another category for the search results.
* Additionally the stats must load remotely on a background thread which means the
* preferences are not available to collect for search when the settings first load.
*/
@SuppressWarnings({"unused", "deprecation"})
public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
public SponsorBlockStatsPreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public SponsorBlockStatsPreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SponsorBlockStatsPreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onAttachedToActivity() {
try {
super.onAttachedToActivity();
Logger.printDebug(() -> "Updating SB stats UI");
final boolean enabled = Settings.SB_ENABLED.get();
setEnabled(enabled);
removeAll();
if (!SponsorBlockSettings.userHasSBPrivateId()) {
// User has never voted or created any segments. Only local stats exist.
addLocalUserStats();
return;
}
Preference loadingPlaceholderPreference = new Preference(getContext());
loadingPlaceholderPreference.setEnabled(false);
addPreference(loadingPlaceholderPreference);
if (enabled) {
loadingPlaceholderPreference.setTitle(str("revanced_sb_stats_loading"));
Utils.runOnBackgroundThread(() -> {
UserStats stats = SBRequester.retrieveUserStats();
Utils.runOnMainThread(() -> { // get back on main thread to modify UI elements
addUserStats(loadingPlaceholderPreference, stats);
addLocalUserStats();
});
});
} else {
loadingPlaceholderPreference.setTitle(str("revanced_sb_stats_sb_disabled"));
}
} catch (Exception ex) {
Logger.printException(() -> "onAttachedToActivity failure", ex);
}
}
private void addUserStats(Preference loadingPlaceholder, @Nullable UserStats stats) {
Utils.verifyOnMainThread();
try {
if (stats == null) {
loadingPlaceholder.setTitle(str("revanced_sb_stats_connection_failure"));
return;
}
removeAll();
Context context = getContext();
if (stats.totalSegmentCountIncludingIgnored > 0) {
// If user has not created any segments, there's no reason to set a username.
String userName = stats.userName;
EditTextPreference preference = new ResettableEditTextPreference(context);
preference.setTitle(fromHtml(str("revanced_sb_stats_username", userName)));
preference.setSummary(str("revanced_sb_stats_username_change"));
preference.setText(userName);
preference.setOnPreferenceChangeListener((preference1, value) -> {
Utils.runOnBackgroundThread(() -> {
String newUserName = (String) value;
String errorMessage = SBRequester.setUsername(newUserName);
Utils.runOnMainThread(() -> {
if (errorMessage == null) {
preference.setTitle(fromHtml(str("revanced_sb_stats_username", newUserName)));
preference.setText(newUserName);
Utils.showToastLong(str("revanced_sb_stats_username_changed"));
} else {
preference.setText(userName); // revert to previous
SponsorBlockUtils.showErrorDialog(errorMessage);
}
});
});
return true;
});
addPreference(preference);
}
{
// Number of segment submissions (does not include ignored segments).
Preference preference = new Preference(context);
String formatted = SponsorBlockUtils.getNumberOfSkipsString(stats.segmentCount);
preference.setTitle(fromHtml(str("revanced_sb_stats_submissions", formatted)));
preference.setSummary(str("revanced_sb_stats_submissions_sum"));
if (stats.totalSegmentCountIncludingIgnored == 0) {
preference.setSelectable(false);
} else {
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sb.ltn.fi/userid/" + stats.publicUserId));
preference1.getContext().startActivity(i);
return true;
});
}
addPreference(preference);
}
{
// "user reputation". Usually not useful since it appears most users have zero reputation.
// But if there is a reputation then show it here.
Preference preference = new Preference(context);
preference.setTitle(fromHtml(str("revanced_sb_stats_reputation", stats.reputation)));
preference.setSelectable(false);
if (stats.reputation != 0) {
addPreference(preference);
}
}
{
// Time saved for other users.
Preference preference = new Preference(context);
String stats_saved;
String stats_saved_sum;
if (stats.totalSegmentCountIncludingIgnored == 0) {
stats_saved = str("revanced_sb_stats_saved_zero");
stats_saved_sum = str("revanced_sb_stats_saved_sum_zero");
} else {
stats_saved = str("revanced_sb_stats_saved",
SponsorBlockUtils.getNumberOfSkipsString(stats.viewCount));
stats_saved_sum = str("revanced_sb_stats_saved_sum",
SponsorBlockUtils.getTimeSavedString((long) (60 * stats.minutesSaved)));
}
preference.setTitle(fromHtml(stats_saved));
preference.setSummary(fromHtml(stats_saved_sum));
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sponsor.ajay.app/stats/"));
preference1.getContext().startActivity(i);
return false;
});
addPreference(preference);
}
} catch (Exception ex) {
Logger.printException(() -> "addUserStats failure", ex);
}
}
private void addLocalUserStats() {
// Time the user saved by using SB.
Preference preference = new Preference(getContext());
Runnable updateStatsSelfSaved = () -> {
String formatted = SponsorBlockUtils.getNumberOfSkipsString(
Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.get());
preference.setTitle(fromHtml(str("revanced_sb_stats_self_saved", formatted)));
String formattedSaved = SponsorBlockUtils.getTimeSavedString(
Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.get() / 1000);
preference.setSummary(fromHtml(str("revanced_sb_stats_self_saved_sum", formattedSaved)));
};
updateStatsSelfSaved.run();
preference.setOnPreferenceClickListener(preference1 -> {
new AlertDialog.Builder(preference1.getContext())
.setTitle(str("revanced_sb_stats_self_saved_reset_title"))
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault();
Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault();
updateStatsSelfSaved.run();
})
.setNegativeButton(android.R.string.no, null).show();
return true;
});
addPreference(preference);
}
}

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
version = 5.23.0-dev.6
version = 5.24.0-dev.5

View File

@@ -240,6 +240,10 @@ public final class app/revanced/patches/instagram/ads/HideAdsPatchKt {
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/instagram/misc/signature/SignatureCheckPatchKt {
public static final fun getSignatureCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/irplus/ad/RemoveAdsPatchKt {
public static final fun getRemoveAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -872,6 +876,10 @@ public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLink
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/widgets/FixThirdPartyLaunchersWidgetsKt {
public static final fun getFixThirdPartyLaunchersWidgets ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/navbar/PremiumNavbarTabPatchKt {
public static final fun getPremiumNavbarTabPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1216,6 +1224,10 @@ public final class app/revanced/patches/youtube/layout/hide/player/flyoutmenupan
public static final fun getHidePlayerFlyoutMenuPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/layout/hide/relatedvideooverlay/HideRelatedVideoOverlayPatchKt {
public static final fun getHideRelatedVideoOverlayPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/layout/hide/rollingnumber/DisableRollingNumberAnimationPatchKt {
public static final fun getDisableRollingNumberAnimationPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1460,8 +1472,10 @@ public final class app/revanced/patches/youtube/misc/settings/PreferenceScreen :
public final fun getGENERAL_LAYOUT ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getMISC ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getPLAYER ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getRETURN_YOUTUBE_DISLIKE ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getSEEKBAR ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getSHORTS ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getSPONSORBLOCK ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getSWIPE_CONTROLS ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
public final fun getVIDEO ()Lapp/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen$Screen;
}

View File

@@ -9,6 +9,5 @@ internal val adInjectorFingerprint = fingerprint {
parameters("L", "L")
strings(
"SponsoredContentController.insertItem",
"SponsoredContentController::Delivery",
)
}

View File

@@ -0,0 +1,20 @@
package app.revanced.patches.instagram.misc.signature
import app.revanced.patcher.fingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val isValidSignatureClassFingerprint = fingerprint {
strings("The provider for uri '", "' is not trusted: ")
}
internal val isValidSignatureMethodFingerprint = fingerprint {
parameters("L", "Z")
returns("Z")
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<MethodReference>()?.name == "keySet"
} >= 0
}
}

View File

@@ -0,0 +1,19 @@
package app.revanced.patches.instagram.misc.signature
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val signatureCheckPatch = bytecodePatch(
name = "Disable signature check",
description = "Disables the signature check that causes the app to crash on startup."
) {
compatibleWith("com.instagram.android"("378.0.0.52.68"))
execute {
isValidSignatureMethodFingerprint
.match(isValidSignatureClassFingerprint.classDef)
.method
.returnEarly(true)
}
}

View File

@@ -4,10 +4,10 @@ import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val jwUtilCreateAdvertisementFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
internal val jwPlayerConfigFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
custom { methodDef, classDef ->
classDef.type == "Lnl/sanomamedia/android/nu/video/util/JWUtil;" && methodDef.name == "createAdvertising"
classDef.type == "Lcom/jwplayer/pub/api/configuration/PlayerConfig${'$'}Builder;" && methodDef.name == "advertisingConfig"
}
}

View File

@@ -2,8 +2,11 @@ package app.revanced.patches.nunl.ads
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused")
@@ -11,23 +14,15 @@ val hideAdsPatch = bytecodePatch(
name = "Hide ads",
description = "Hide ads and sponsored articles in list pages and remove pre-roll ads on videos.",
) {
compatibleWith("nl.sanomamedia.android.nu"("11.0.0", "11.0.1", "11.1.0"))
compatibleWith("nl.sanomamedia.android.nu"("11.3.0"))
dependsOn(sharedExtensionPatch("nunl", mainActivityOnCreateHook))
execute {
// Disable video pre-roll ads.
// Whenever the app tries to create an ad via JWUtils.createAdvertising, don't actually tell the underlying JWPlayer library to do so => JWPlayer will not display ads.
jwUtilCreateAdvertisementFingerprint.method.addInstructions(
0,
"""
new-instance v0, Lcom/jwplayer/pub/api/configuration/ads/VastAdvertisingConfig${'$'}Builder;
invoke-direct { v0 }, Lcom/jwplayer/pub/api/configuration/ads/VastAdvertisingConfig${'$'}Builder;-><init>()V
invoke-virtual { v0 }, Lcom/jwplayer/pub/api/configuration/ads/VastAdvertisingConfig${'$'}Builder;->build()Lcom/jwplayer/pub/api/configuration/ads/VastAdvertisingConfig;
move-result-object v0
return-object v0
""",
)
// Whenever the app tries to define the advertising config for JWPlayer, don't set the advertising config and directly return.
val iputInstructionIndex = jwPlayerConfigFingerprint.method.indexOfFirstInstructionOrThrow(Opcode.IPUT_OBJECT)
jwPlayerConfigFingerprint.method.removeInstructions(iputInstructionIndex, 1)
// Filter injected content from API calls out of lists.
arrayOf(screenMapperFingerprint, nextPageRepositoryImplFingerprint).forEach {

View File

@@ -0,0 +1,10 @@
package app.revanced.patches.spotify.misc.widgets
import app.revanced.patcher.fingerprint
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.Opcode
internal val canBindAppWidgetPermissionFingerprint = fingerprint {
strings("android.permission.BIND_APPWIDGET")
opcodes(Opcode.AND_INT_LIT8)
}

View File

@@ -0,0 +1,19 @@
package app.revanced.patches.spotify.misc.widgets
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val fixThirdPartyLaunchersWidgets = bytecodePatch(
name = "Fix third party launchers widgets",
description = "Fixes Spotify widgets not working in third party launchers, like Nova Launcher.",
) {
compatibleWith("com.spotify.music")
execute {
// Only system app launchers are granted the BIND_APPWIDGET permission.
// Override the method that checks for it to always return true, as this permission is not actually required
// for the widgets to work.
canBindAppWidgetPermissionFingerprint.method.returnEarly(true)
}
}

View File

@@ -222,7 +222,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
SwitchPreference("revanced_hide_movies_section"),
SwitchPreference("revanced_hide_notify_me_button"),
SwitchPreference("revanced_hide_playables"),
SwitchPreference("revanced_hide_search_result_recommendations"),
SwitchPreference("revanced_hide_search_result_recommendation_labels"),
SwitchPreference("revanced_hide_show_more_button"),
SwitchPreference("revanced_hide_doodles"),
)

View File

@@ -0,0 +1,18 @@
package app.revanced.patches.youtube.layout.hide.relatedvideooverlay
import app.revanced.patcher.fingerprint
import app.revanced.util.literal
internal val relatedEndScreenResultsParentFingerprint = fingerprint {
returns("V")
literal{ appRelatedEndScreenResults }
}
internal val relatedEndScreenResultsFingerprint = fingerprint {
returns("V")
parameters(
"I",
"Z",
"I",
)
}

View File

@@ -0,0 +1,83 @@
package app.revanced.patches.youtube.layout.hide.relatedvideooverlay
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patcher.util.smali.ExternalLabel
internal var appRelatedEndScreenResults = -1L
private set
private val hideRelatedVideoOverlayResourcePatch = resourcePatch {
dependsOn(
resourceMappingPatch,
)
execute {
appRelatedEndScreenResults = resourceMappings[
"layout",
"app_related_endscreen_results",
]
}
}
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/HideRelatedVideoOverlayPatch;"
@Suppress("unused")
val hideRelatedVideoOverlayPatch = bytecodePatch(
name = "Hide related video overlay",
description = "Adds an option to hide the related video overlay shown when swiping up in fullscreen.",
) {
dependsOn(
settingsPatch,
sharedExtensionPatch,
addResourcesPatch,
hideRelatedVideoOverlayResourcePatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42",
"19.43.41",
"19.47.53",
"20.07.39",
"20.12.46",
)
)
execute {
addResources("youtube", "layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_hide_related_video_overlay")
)
relatedEndScreenResultsFingerprint.match(
relatedEndScreenResultsParentFingerprint.originalClassDef
).method.apply {
addInstructionsWithLabels(
0,
"""
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideRelatedVideoOverlay()Z
move-result v0
if-eqz v0, :show
return-void
""",
ExternalLabel("show", getInstruction(0))
)
}
}
}

View File

@@ -6,7 +6,10 @@ import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
@@ -15,15 +18,18 @@ import app.revanced.patches.youtube.misc.playservice.is_19_33_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_10_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.addSettingPreference
import app.revanced.patches.youtube.misc.settings.newIntent
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.conversionContextFingerprintToString
import app.revanced.patches.youtube.shared.rollingNumberTextViewAnimationUpdateFingerprint
import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
import app.revanced.patches.youtube.video.videoid.hookVideoId
import app.revanced.patches.youtube.video.videoid.videoIdPatch
import app.revanced.util.*
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.findFreeRegister
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.returnLate
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@@ -68,15 +74,24 @@ val returnYouTubeDislikePatch = bytecodePatch(
execute {
addResources("youtube", "layout.returnyoutubedislike.returnYouTubeDislikePatch")
addSettingPreference(
IntentPreference(
key = "revanced_settings_screen_09",
titleKey = "revanced_ryd_settings_title",
summaryKey = null,
icon = "@drawable/revanced_settings_screen_09_ryd",
layout = "@layout/preference_with_icon",
intent = newIntent("revanced_ryd_settings_intent"),
PreferenceScreen.RETURN_YOUTUBE_DISLIKE.addPreferences(
SwitchPreference("revanced_ryd_enabled"),
SwitchPreference("revanced_ryd_shorts"),
SwitchPreference("revanced_ryd_dislike_percentage"),
SwitchPreference("revanced_ryd_compact_layout"),
SwitchPreference("revanced_ryd_estimated_like"),
SwitchPreference("revanced_ryd_toast_on_connection_error"),
NonInteractivePreference(
key = "revanced_ryd_attribution",
tag = "app.revanced.extension.youtube.returnyoutubedislike.ui.ReturnYouTubeDislikeAboutPreference",
selectable = true,
),
PreferenceCategory(
key = "revanced_ryd_statistics_category",
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
preferences = emptySet(), // Preferences are added by custom class at runtime.
tag = "app.revanced.extension.youtube.returnyoutubedislike.ui.ReturnYouTubeDislikeDebugStatsPreferenceCategory"
)
)
// region Inject newVideoLoaded event handler to update dislikes when a new video is loaded.

View File

@@ -12,12 +12,13 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playercontrols.*
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.settings.addSettingPreference
import app.revanced.patches.youtube.misc.settings.newIntent
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.*
import app.revanced.patches.youtube.video.information.onCreateHook
@@ -43,15 +44,32 @@ private val sponsorBlockResourcePatch = resourcePatch {
execute {
addResources("youtube", "layout.sponsorblock.sponsorBlockResourcePatch")
addSettingPreference(
IntentPreference(
key = "revanced_settings_screen_10",
titleKey = "revanced_sb_settings_title",
summaryKey = null,
icon = "@drawable/revanced_settings_screen_10_sb",
layout = "@layout/preference_with_icon",
intent = newIntent("revanced_sb_settings_intent"),
PreferenceScreen.SPONSORBLOCK.addPreferences(
// SB setting is old code with lots of custom preferences and updating behavior.
// Added as a preference group and not a fragment so the preferences are searchable.
PreferenceCategory(
key = "revanced_settings_screen_10_sponsorblock",
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
preferences = emptySet(), // Preferences are added by custom class at runtime.
tag = "app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup"
),
PreferenceCategory(
key = "revanced_sb_stats",
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
preferences = emptySet(), // Preferences are added by custom class at runtime.
tag = "app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockStatsPreferenceCategory"
),
PreferenceCategory(
key = "revanced_sb_about",
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
preferences = setOf(
NonInteractivePreference(
key = "revanced_sb_about_api",
tag = "app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockAboutPreference",
selectable = true,
)
)
)
)
arrayOf(

View File

@@ -54,6 +54,7 @@ val changeStartPagePatch = bytecodePatch(
ListPreference(
key = "revanced_change_start_page",
summaryKey = null,
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference"
),
SwitchPreference("revanced_change_start_page_always")
)

View File

@@ -74,6 +74,7 @@ private val settingsResourcePatch = resourcePatch {
arrayOf(
ResourceGroup("drawable",
"revanced_settings_cursor.xml",
"revanced_settings_icon.xml",
"revanced_settings_screen_00_about.xml",
"revanced_settings_screen_01_ads.xml",
@@ -84,12 +85,16 @@ private val settingsResourcePatch = resourcePatch {
"revanced_settings_screen_06_shorts.xml",
"revanced_settings_screen_07_seekbar.xml",
"revanced_settings_screen_08_swipe_controls.xml",
"revanced_settings_screen_09_ryd.xml",
"revanced_settings_screen_10_sb.xml",
"revanced_settings_screen_09_return_youtube_dislike.xml",
"revanced_settings_screen_10_sponsorblock.xml",
"revanced_settings_screen_11_misc.xml",
"revanced_settings_screen_12_video.xml",
),
ResourceGroup("layout", "revanced_settings_with_toolbar.xml"),
ResourceGroup("layout",
"revanced_preference_with_icon_no_search_result.xml",
"revanced_search_suggestion_item.xml",
"revanced_settings_with_toolbar.xml"),
ResourceGroup("menu", "revanced_search_menu.xml")
).forEach { resourceGroup ->
copyResources("settings", resourceGroup)
}
@@ -188,6 +193,7 @@ val settingsPatch = bytecodePatch(
}
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
SwitchPreference("revanced_settings_search_history"),
SwitchPreference("revanced_show_menu_icons")
)
@@ -201,7 +207,8 @@ val settingsPatch = bytecodePatch(
),
ListPreference(
key = "revanced_language",
summaryKey = null
summaryKey = null,
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference"
)
)
@@ -347,10 +354,20 @@ object PreferenceScreen : BasePreferenceScreen() {
layout = "@layout/preference_with_icon",
sorting = Sorting.UNSORTED,
)
// RYD and SB are items 9 and 10.
// Menus are added in their own patch because they use an Intent and not a Screen.
val RETURN_YOUTUBE_DISLIKE = Screen(
key = "revanced_settings_screen_09_return_youtube_dislike",
summaryKey = null,
icon = "@drawable/revanced_settings_screen_09_return_youtube_dislike",
layout = "@layout/preference_with_icon",
sorting = Sorting.UNSORTED,
)
val SPONSORBLOCK = Screen(
key = "revanced_settings_screen_10_sponsorblock",
summaryKey = null,
icon = "@drawable/revanced_settings_screen_10_sponsorblock",
layout = "@layout/preference_with_icon",
sorting = Sorting.UNSORTED,
)
val MISC = Screen(
key = "revanced_settings_screen_11_misc",
summaryKey = null,

View File

@@ -62,7 +62,8 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
summaryKey = null,
// Language strings are declared in Setting patch.
entriesKey = "revanced_language_entries",
entryValuesKey = "revanced_language_entry_values"
entryValuesKey = "revanced_language_entry_values",
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference"
),
SwitchPreference("revanced_spoof_video_streams_ios_force_avc"),
SwitchPreference("revanced_spoof_streaming_data_stats_for_nerds"),

View File

@@ -38,6 +38,7 @@ internal val rememberPlaybackSpeedPatch = bytecodePatch {
// Entries and values are set by the extension code based on the actual speeds available.
entriesKey = null,
entryValuesKey = null,
tag = "app.revanced.extension.youtube.settings.preference.CustomVideoSpeedListPreference"
),
SwitchPreference("revanced_remember_playback_speed_last_selected")
)

View File

@@ -65,35 +65,6 @@ fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude: Int): In
val instruction = getInstruction(i)
val instructionRegisters = instruction.registersUsed
if (instruction.isReturnInstruction) {
usedRegisters.addAll(instructionRegisters)
// Use lowest register that hasn't been encountered.
val freeRegister = (0 until implementation!!.registerCount).find {
it !in usedRegisters
}
if (freeRegister != null) {
return freeRegister
}
if (bestFreeRegisterFound != null) {
return bestFreeRegisterFound
}
// Somehow every method register was read from before any register was wrote to.
// In practice this never occurs.
throw IllegalArgumentException("Could not find a free register from startIndex: " +
"$startIndex excluding: $registersToExclude")
}
if (instruction.isBranchInstruction) {
if (bestFreeRegisterFound != null) {
return bestFreeRegisterFound
}
// This method is simple and does not follow branching.
throw IllegalArgumentException("Encountered a branch statement before a free register could be found")
}
val writeRegister = instruction.writeRegister
if (writeRegister != null) {
if (writeRegister !in usedRegisters) {
@@ -114,6 +85,32 @@ fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude: Int): In
}
usedRegisters.addAll(instructionRegisters)
if (instruction.isBranchInstruction) {
if (bestFreeRegisterFound != null) {
return bestFreeRegisterFound
}
// This method is simple and does not follow branching.
throw IllegalArgumentException("Encountered a branch statement before a free register could be found")
}
if (instruction.isReturnInstruction) {
// Use lowest register that hasn't been encountered.
val freeRegister = (0 until implementation!!.registerCount).find {
it !in usedRegisters
}
if (freeRegister != null) {
return freeRegister
}
if (bestFreeRegisterFound != null) {
return bestFreeRegisterFound
}
// Somehow every method register was read from before any register was wrote to.
// In practice this never occurs.
throw IllegalArgumentException("Could not find a free register from startIndex: " +
"$startIndex excluding: $registersToExclude")
}
}
// Some methods can have array payloads at the end of the method after a return statement.
@@ -504,7 +501,7 @@ fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): I
* @see indexOfFirstInstructionOrThrow
*/
fun Method.indexOfFirstInstruction(startIndex: Int = 0, filter: Instruction.() -> Boolean): Int {
var instructions = this.implementation!!.instructions
var instructions = this.implementation?.instructions ?: return -1
if (startIndex != 0) {
instructions = instructions.drop(startIndex)
}
@@ -570,7 +567,7 @@ fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode
* @see indexOfFirstInstructionReversedOrThrow
*/
fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, filter: Instruction.() -> Boolean): Int {
var instructions = this.implementation!!.instructions
var instructions = this.implementation?.instructions ?: return -1
if (startIndex != null) {
instructions = instructions.take(startIndex + 1)
}

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -794,6 +794,11 @@ Second \"item\" text"</string>
الإعدادات ← التشغيل ← تشغيل الفيديو التالي تلقائيًا"</string>
<string name="revanced_end_screen_suggested_video_summary_off">يتم عرض الفيديو المقترح في شاشة النهاية</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">إخفاء واجهة الفيديوهات ذات الصلة في وضع ملء الشاشة</string>
<string name="revanced_hide_related_video_overlay_summary_on">تم إخفاء واجهة الفيديوهات ذات الصلة</string>
<string name="revanced_hide_related_video_overlay_summary_off">يتم عرض واجهة الفيديوهات ذات الصلة</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">إخفاء الطابع الزمني للفيديو</string>
<string name="revanced_hide_timestamp_summary_on">تم إخفاء الطابع الزمني</string>

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -793,6 +793,11 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
Avtomatik oynatma YouTube ayarlarında dəyişdirilə bilər: Ayarlar → Oxunuş → Növbəti videonu avtomatik oxudun"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Son ekranda bildirilən video göstərilir</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Əlaqəli video örtüyünü tam ekranda gizlət</string>
<string name="revanced_hide_related_video_overlay_summary_on">Əlaqəli video örtüyü gizlədilib</string>
<string name="revanced_hide_related_video_overlay_summary_off">Əlaqəli video örtüyü göstərilir</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Video vaxt möhürünü gizlət</string>
<string name="revanced_hide_timestamp_summary_on">Vaxt möhürü gizlidir</string>

View File

@@ -794,6 +794,11 @@ Second \"item\" text"</string>
Налады → Прайграванне → Аўтаматычнае прайграванне наступнага відэа"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Паказваць прапанаванае відэа на канчатковым экране</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Схаваць накладку звязанага відэа ў поўнаэкранным рэжыме</string>
<string name="revanced_hide_related_video_overlay_summary_on">Накладка звязанага відэа схавана</string>
<string name="revanced_hide_related_video_overlay_summary_off">Накладка звязанага відэа паказана</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Схаваць метку часу відэа</string>
<string name="revanced_hide_timestamp_summary_on">Метка часу схавана</string>

View File

@@ -794,6 +794,11 @@ Second \"item\" text"</string>
Настройки → Възпроизвеждане → Автоматично пускане на следващото видео"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Предложеното видео в края на екрана е показано</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Скриване на наслагването на свързани видеоклипове в цял екран</string>
<string name="revanced_hide_related_video_overlay_summary_on">Наслагването на свързани видеоклипове е скрито</string>
<string name="revanced_hide_related_video_overlay_summary_off">Наслагването на свързани видеоклипове е показано</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Скриване на клеймото за време на видеоклипа</string>
<string name="revanced_hide_timestamp_summary_on">Скрито</string>

View File

@@ -794,6 +794,11 @@ YouTube সেটিংসে অটো প্লে পরিবর্তন
সেটিংস → প্লেব্যাক → অটো প্লে পরবর্তী ভিডিও"</string>
<string name="revanced_end_screen_suggested_video_summary_off">শেষ স্ক্রীনে প্রস্তাবিত ভিডিও দেখানো হয়েছে</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">পূর্ণ-স্ক্রীনে সম্পর্কিত ভিডিও ওভারলে লুকান</string>
<string name="revanced_hide_related_video_overlay_summary_on">সম্পর্কিত ভিডিও ওভারলে লুকানো আছে</string>
<string name="revanced_hide_related_video_overlay_summary_off">সম্পর্কিত ভিডিও ওভারলে দেখানো হয়েছে</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">ভিডিওর সময়স্ট্যাম্প লুকান</string>
<string name="revanced_hide_timestamp_summary_on">সময়স্ট্যাম্প লুকিয়ে রয়েছে</string>

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -794,6 +794,11 @@ La reproducció automàtica es pot canviar a la configuració de YouTube:
Configuració → Reproducció → Reprodueix el vídeo següent automàticament"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Es mostra el vídeo suggerit de la pantalla final</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Amaga la superposició de vídeos relacionats a pantalla completa</string>
<string name="revanced_hide_related_video_overlay_summary_on">La superposició de vídeos relacionats està amagada</string>
<string name="revanced_hide_related_video_overlay_summary_off">Es mostra la superposició de vídeos relacionats</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Amagar segell de temps del vídeo</string>
<string name="revanced_hide_timestamp_summary_on">El segell de temps està ocult</string>

View File

@@ -794,6 +794,11 @@ Automatické přehrávání lze změnit v nastavení YouTube:
Nastavení → Přehrávání → Automatické přehrávání dalšího videa"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Navrhované video na konci obrazovky se zobrazuje</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Skrýt překryv souvisejícího videa v režimu celé obrazovky</string>
<string name="revanced_hide_related_video_overlay_summary_on">Překryv souvisejícího videa je skrytý</string>
<string name="revanced_hide_related_video_overlay_summary_off">Překryv souvisejícího videa je zobrazen</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Skrýt čas videa</string>
<string name="revanced_hide_timestamp_summary_on">Čas je skrytý</string>

View File

@@ -757,6 +757,11 @@ Automatisk afspilning kan ændres i YouTube-indstillinger:
Indstillinger → Afspilning → Afspil næste video automatisk"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Foreslået video på slutskærmen vises</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Skjul relateret video-overlay i fuldskærm</string>
<string name="revanced_hide_related_video_overlay_summary_on">Relateret video-overlay er skjult</string>
<string name="revanced_hide_related_video_overlay_summary_off">Relateret video-overlay vises</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Skjul tidsstempel på video</string>
<string name="revanced_hide_timestamp_summary_on">Tidsstempel er skjult</string>

View File

@@ -787,6 +787,11 @@ Die automatische Wiedergabe kann in den YouTube-Einstellungen geändert werden:
Einstellungen → Wiedergabe → Nächstes Video automatisch abspielen"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Das vorgeschlagene Video auf dem Endbildschirm wird angezeigt</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Zugehöriges Video-Overlay im Vollbildmodus ausblenden</string>
<string name="revanced_hide_related_video_overlay_summary_on">Zugehöriges Video-Overlay ist ausgeblendet</string>
<string name="revanced_hide_related_video_overlay_summary_off">Zugehöriges Video-Overlay wird angezeigt</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Verstecke Video-Zeitstempel</string>
<string name="revanced_hide_timestamp_summary_on">Zeitstempel ist ausgeblendet</string>

View File

@@ -796,6 +796,11 @@ Second \"item\" text"</string>
Ρυθμίσεις → Αναπαραγωγή→ Αυτόματη αναπαραγωγή επόμενου βίντεο"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Εμφανίζεται</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Απόκρυψη επικάλυψης σχετικού βίντεο σε πλήρη οθόνη</string>
<string name="revanced_hide_related_video_overlay_summary_on">Η επικάλυψη σχετικού βίντεο είναι κρυφή</string>
<string name="revanced_hide_related_video_overlay_summary_off">Εμφανίζεται η επικάλυψη σχετικού βίντεο</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Χρονική πρόοδος βίντεο</string>
<string name="revanced_hide_timestamp_summary_on">Κρυμμένη</string>
@@ -851,8 +856,8 @@ Second \"item\" text"</string>
<string name="revanced_ryd_estimated_like_summary_on">Τα βίντεο με απενεργοποιημένα «Μου αρέσει» εμφανίζουν έναν εκτιμώμενο αριθμό «Μου αρέσει»</string>
<string name="revanced_ryd_estimated_like_summary_off">Τα εκτιμώμενα «Μου αρέσει» δεν εμφανίζονται</string>
<string name="revanced_ryd_toast_on_connection_error_title">Εμφάνιση μηνύματος αν το API δεν είναι διαθέσιμο</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">Να εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το Return YouTube Dislike δεν είναι διαθέσιμο</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">Να μην εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το Return YouTube Dislike δεν είναι διαθέσιμο</string>
<string name="revanced_ryd_toast_on_connection_error_summary_on">Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το Return YouTube Dislike δεν είναι διαθέσιμο</string>
<string name="revanced_ryd_toast_on_connection_error_summary_off">Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το Return YouTube Dislike δεν είναι διαθέσιμο</string>
<string name="revanced_ryd_about">Σχετικά με</string>
<string name="revanced_ryd_attribution_summary">Τα δεδομένα Dislike παρέχονται από το API του Return YouTube Dislike. Πατήστε εδώ για να μάθετε περισσότερα</string>
<!-- Statistic strings are shown in the settings only when ReVanced debug mode is enabled. Typical users will never see these. -->
@@ -911,8 +916,8 @@ Second \"item\" text"</string>
<string name="revanced_sb_enable_auto_hide_skip_segment_button_sum_on">Το κουμπί παράλειψης κρύβεται μετά από μερικά δευτερόλεπτα</string>
<string name="revanced_sb_enable_auto_hide_skip_segment_button_sum_off">Το κουμπί παράλειψης εμφανίζεται σε όλο το τμήμα</string>
<string name="revanced_sb_general_skiptoast">Εμφάνιση μηνύματος κατά την παράλειψη</string>
<string name="revanced_sb_general_skiptoast_sum_on">Να εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης όταν ένα τμήμα παραλείπεται αυτόματα. Πατήστε για να δείτε ένα παράδειγμα</string>
<string name="revanced_sb_general_skiptoast_sum_off">Να μην εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης. Πατήστε για να δείτε ένα παράδειγμα</string>
<string name="revanced_sb_general_skiptoast_sum_on">Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης όταν ένα τμήμα παραλείπεται αυτόματα. Πατήστε για να δείτε ένα παράδειγμα</string>
<string name="revanced_sb_general_skiptoast_sum_off">Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης. Πατήστε για να δείτε ένα παράδειγμα</string>
<string name="revanced_sb_general_time_without">Εμφάνιση μήκους βίντεο χωρίς τα τμήματα</string>
<string name="revanced_sb_general_time_without_sum_on">Εμφανίζεται το μήκος βίντεο μείον όλα τα τμήματα, σε παρένθεση δίπλα στο πλήρες μήκος βίντεο</string>
<string name="revanced_sb_general_time_without_sum_off">Εμφανίζεται το πλήρες μήκος του βίντεο</string>
@@ -931,8 +936,8 @@ Second \"item\" text"</string>
<string name="revanced_sb_guidelines_popup_open">Δείξτε μου</string>
<string name="revanced_sb_general">Γενικά</string>
<string name="revanced_sb_toast_on_connection_error_title">Εμφάνιση μηνύματος αν το API δεν είναι διαθέσιμο</string>
<string name="revanced_sb_toast_on_connection_error_summary_on">Να εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το SponsorBlock δεν είναι διαθέσιμο</string>
<string name="revanced_sb_toast_on_connection_error_summary_off">Να μην εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το SponsorBlock δεν είναι διαθέσιμο</string>
<string name="revanced_sb_toast_on_connection_error_summary_on">Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το SponsorBlock δεν είναι διαθέσιμο</string>
<string name="revanced_sb_toast_on_connection_error_summary_off">Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το SponsorBlock δεν είναι διαθέσιμο</string>
<string name="revanced_sb_general_skipcount">Mετρητής παραλείψεων τμημάτων</string>
<string name="revanced_sb_general_skipcount_sum_on">Επιτρέπει στον πίνακα κατάταξης SponsorBlock να γνωρίζει πόσος χρόνος εξοικονομήθηκε. Αποστέλλεται ένα μήνυμα στον πίνακα κατάταξης κάθε φορά που παραλείπεται ένα τμήμα</string>
<string name="revanced_sb_general_skipcount_sum_off">Ο μετρητής παραλείψεων δεν είναι ενεργός</string>
@@ -1261,8 +1266,8 @@ Second \"item\" text"</string>
Πατήστε για να μάθετε περισσότερα για το DeArrow"</string>
<string name="revanced_alt_thumbnail_dearrow_connection_toast_title">Εμφάνιση μηνύματος αν το API δεν είναι διαθέσιμο</string>
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_on">Να εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το DeArrow δεν είναι διαθέσιμο</string>
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_off">Να μην εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το DeArrow δεν είναι διαθέσιμο</string>
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_on">Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το DeArrow δεν είναι διαθέσιμο</string>
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_off">Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης αν το DeArrow δεν είναι διαθέσιμο</string>
<string name="revanced_alt_thumbnail_dearrow_api_url_title">Διεύθυνση API του DeArrow</string>
<string name="revanced_alt_thumbnail_dearrow_api_url_summary">Η διεύθυνση URL του τελικού σημείου αποθήκευσης μικρογραφιών DeArrow</string>
<string name="revanced_alt_thumbnail_stills_about_title">Λήψεις ακίνητων καρέ</string>

View File

@@ -791,6 +791,11 @@ La reproducción automática se puede cambiar en la configuración de YouTube:
Configuración → Reproducción → Reproducir el siguiente vídeo automáticamente"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Se muestra el vídeo sugerido de la pantalla final</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Ocultar la superposición de videos relacionados en pantalla completa</string>
<string name="revanced_hide_related_video_overlay_summary_on">Se oculta la superposición de videos relacionados</string>
<string name="revanced_hide_related_video_overlay_summary_off">Se muestra la superposición de videos relacionados</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Ocultar fecha y hora de vídeo</string>
<string name="revanced_hide_timestamp_summary_on">Marca de tiempo oculta</string>

View File

@@ -794,6 +794,11 @@ Automaatesitust saab muuta YouTube'i seadetes:
Seaded → Taasesitus → Esita järgmine video automaatselt"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Lõpuekraani soovitatud video on näidatud</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Peida seotud video ülekatte täisekraanil</string>
<string name="revanced_hide_related_video_overlay_summary_on">Seotud video ülekatte on peidetud</string>
<string name="revanced_hide_related_video_overlay_summary_off">Seotud video ülekatte on näidatud</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Peida video ajamärk</string>
<string name="revanced_hide_timestamp_summary_on">Ajamärk on peidetud</string>

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -154,6 +154,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -791,6 +791,11 @@ Automaattista toistoa voidaan muuttaa YouTube-asetuksissa:
Asetukset → Toisto → Toista seuraava video automaattisesti"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Loppunäytön ehdotettu video näytetään</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Piilota liittyvien videoiden peittokuva kokoruututilassa</string>
<string name="revanced_hide_related_video_overlay_summary_on">Liittyvien videoiden peittokuva on piilotettu</string>
<string name="revanced_hide_related_video_overlay_summary_off">Liittyvien videoiden peittokuva näytetään</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Piilota videon aikaleima</string>
<string name="revanced_hide_timestamp_summary_on">Aikaleima on piilotettu</string>

View File

@@ -792,6 +792,11 @@ Maaaring baguhin ang Autoplay sa mga setting ng YouTube:
Mga Setting → Pag-playback → I-autoplay ang susunod na video"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Ipinapakita ang iminungkahing video sa dulo ng screen</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Itago ang kaugnay na video overlay sa fullscreen</string>
<string name="revanced_hide_related_video_overlay_summary_on">Nakatago ang kaugnay na video overlay</string>
<string name="revanced_hide_related_video_overlay_summary_off">Ipinapakita ang kaugnay na video overlay</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Itago ang timestamp ng video</string>
<string name="revanced_hide_timestamp_summary_on">Nakatago ang timestamp</string>

View File

@@ -794,6 +794,11 @@ La lecture automatique peut être modifiée dans les paramètres de YouTube :
Paramètres → Lecture → Lecture automatique de la vidéo suivante"</string>
<string name="revanced_end_screen_suggested_video_summary_off">L\'écran de fin suggérant une vidéo est affiché</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Masquer l\'overlay de vidéos similaires en plein écran</string>
<string name="revanced_hide_related_video_overlay_summary_on">L\'overlay de vidéos similaires est masqué</string>
<string name="revanced_hide_related_video_overlay_summary_off">L\'overlay de vidéos similaires est affiché</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Masquer le temps écoulé et la durée totale</string>
<string name="revanced_hide_timestamp_summary_on">Le temps écoulé et la durée totale sont masqués</string>

View File

@@ -114,9 +114,9 @@ Brúigh an cnaipe leanúnaí agus ligean athruithe optúimíochta."</string>
<string name="revanced_debug_toast_on_error_title">Taispeáin tósta ar earráid ReVanced</string>
<string name="revanced_debug_toast_on_error_summary_on">Taispeántar toast má tharlaíonn earráid</string>
<string name="revanced_debug_toast_on_error_summary_off">Ní thaispeántar toast má tharlaíonn earráid</string>
<string name="revanced_debug_toast_on_error_user_dialog_message">"Díchumasaíonn an rogha toasts earráide fógraí earráide ReVanced go léir.
<string name="revanced_debug_toast_on_error_user_dialog_message">"Má mhúchtar tóstaí earráide, folaítear gach fógra earráide ReVanced.
bheidh a fhios agat faoi aon imeachtaí neamhghnácha."</string>
chuirfear ar an eolas thú faoi aon imeachtaí gan choinne."</string>
</patch>
<patch id="layout.hide.general.hideLayoutComponentsPatch">
<string name="revanced_hide_album_cards_title">Folaigh cártaí albam</string>
@@ -205,11 +205,11 @@ Ní bheidh a fhios agat faoi aon imeachtaí neamhghnácha."</string>
<string name="revanced_hide_medical_panels_summary_on">Tá painéil leighis i bhfolach</string>
<string name="revanced_hide_medical_panels_summary_off">Taispeántar painéil leighis</string>
<string name="revanced_hide_channel_bar_title">Folaigh barra cainéal</string>
<string name="revanced_hide_channel_bar_summary_on">Tá barra cainéal i bhfolach</string>
<string name="revanced_hide_channel_bar_summary_off">Taispeántar barra cainéal</string>
<string name="revanced_hide_playables_title">Folaigh Rudaí Inimeartha</string>
<string name="revanced_hide_playables_summary_on">rudaí inimeartha i bhfolach</string>
<string name="revanced_hide_playables_summary_off">Taispeántar rudaí inimeartha</string>
<string name="revanced_hide_channel_bar_summary_on"> an barra Cainéal i bhfolach</string>
<string name="revanced_hide_channel_bar_summary_off">Taispeántar barra an chainéil</string>
<string name="revanced_hide_playables_title">Folaigh na heilimintí inseinnte</string>
<string name="revanced_hide_playables_summary_on">na heilimintí inseinnte i bhfolach</string>
<string name="revanced_hide_playables_summary_off">Taispeántar na heilimintí inseinnte</string>
<string name="revanced_hide_quick_actions_title">Folaigh gníomhartha gasta i lánscáileán</string>
<string name="revanced_hide_quick_actions_summary_on">Tá gníomhartha gasta i bhfolach</string>
<string name="revanced_hide_quick_actions_summary_off">Taispeántar gníomhartha tapa</string>
@@ -794,6 +794,11 @@ Is féidir an uathshein a athrú i socruithe YouTube:
Socruithe → Athsheinm → Uathshein físeán eile"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Taispeántar físeán molta deireadh scáileáin</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Folaigh forleagan físeáin ghaolmhair i mód lánscáileáin</string>
<string name="revanced_hide_related_video_overlay_summary_on">Tá forleagan físeáin ghaolmhair i bhfolach</string>
<string name="revanced_hide_related_video_overlay_summary_off">Taispeántar forleagan físeáin ghaolmhair</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Folaigh stampa ama an fhíseáin</string>
<string name="revanced_hide_timestamp_summary_on">Tá stampa ama i bhfolach</string>

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -794,6 +794,11 @@ Az automatikus lejátszás a YouTube beállításaiban módosítható:
Beállítások → Lejátszás → Következő videó automatikus lejátszása"</string>
<string name="revanced_end_screen_suggested_video_summary_off">A befejező képernyőn lévő javasolt videó látható</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Kapcsolódó videó fedőréteg elrejtése teljes képernyőn</string>
<string name="revanced_hide_related_video_overlay_summary_on">A kapcsolódó videó fedőréteg rejtve van</string>
<string name="revanced_hide_related_video_overlay_summary_off">A kapcsolódó videó fedőréteg látható</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Videó időbélyegzőjének elrejtése</string>
<string name="revanced_hide_timestamp_summary_on">Az időbélyegző elrejtve</string>

View File

@@ -794,6 +794,11 @@ MicroG-ի համար մարտկոցի օպտիմալացումը անջատել
Կարգավորումներ → Վերարտադրում → Ավտոմատ նվագարկել հաջորդ տեսանյութը"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Վերջնական էկրանին առաջարկվող տեսանյութը ցուցադրվում է</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Թաքցնել կապակցված տեսանյութի վահանակը լիաէկրան ռեժիմում</string>
<string name="revanced_hide_related_video_overlay_summary_on">Կապակցված տեսանյութի վահանակը թաքցված է</string>
<string name="revanced_hide_related_video_overlay_summary_off">Կապակցված տեսանյութի վահանակը ցուցադրվում է</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Թաքցնել տեսանյութի ժամանակային կետը</string>
<string name="revanced_hide_timestamp_summary_on">Ժամանակային կետը թաքցված է</string>

View File

@@ -794,6 +794,11 @@ Pemutaran otomatis dapat diubah di pengaturan YouTube:
Pengaturan → Pemutaran → Putar otomatis video berikutnya"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Video yang disarankan di layar akhir ditampilkan</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Sembunyikan overlay video terkait dalam layar penuh</string>
<string name="revanced_hide_related_video_overlay_summary_on">Overlay video terkait disembunyikan</string>
<string name="revanced_hide_related_video_overlay_summary_off">Overlay video terkait ditampilkan</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Sembunyikan timestamp video</string>
<string name="revanced_hide_timestamp_summary_on">Timestamp disembunyikan</string>

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -794,6 +794,11 @@ L'autoplay può essere modificato nelle impostazioni di YouTube:
Impostazioni → Riproduzione → Riproduzione automatica video successivo"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Il video suggerito nella schermata finale è mostrato</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Nascondi la sovrapposizione dei video correlati a schermo intero</string>
<string name="revanced_hide_related_video_overlay_summary_on">La sovrapposizione dei video correlati è nascosta</string>
<string name="revanced_hide_related_video_overlay_summary_off">La sovrapposizione dei video correlati è visualizzata</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Nascondi timestamp video</string>
<string name="revanced_hide_timestamp_summary_on">Il timestamp è nascosto</string>

View File

@@ -778,6 +778,8 @@ Second \"item\" text"</string>
הגדרות ← הפעלה ← הפעלה אוטומטית של הסרטון הבא"</string>
<string name="revanced_end_screen_suggested_video_summary_off">סרטון מוצע של מסך סיום מוצג</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">הסתר חותמת זמן של סרטון</string>
<string name="revanced_hide_timestamp_summary_on">חותמת זמן מוסתרת</string>

View File

@@ -333,9 +333,9 @@ MicroG GmsCore に対する電池の最適化を無効にしても、バッテ
<string name="revanced_hide_keyword_content_about_summary">"ホーム / 登録チャンネル / 検索結果からキーワードに合致する動画を除外します
制限事項
• ショート動画はチャンネル名で除外できません
• 一部の UI コンポーネントが残ってしまう場合があります
• キーワードを検索しても、結果が表示されない場合があります"</string>
• ショート動画はチャンネル名で除外されない
• 一部の UI コンポーネントが残ってしまう場合があ
• キーワードを検索したとき、結果が表示されない場合があ"</string>
<string name="revanced_hide_keyword_content_about_whole_words_title">単語全体で合致させる</string>
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc.) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
<string name="revanced_hide_keyword_content_about_whole_words_summary">キーワードを二重引用符で囲むことで、動画のタイトルやチャンネル名の単語の一部とキーワードが合致しないようにできます&lt;br&gt;&lt;br&gt;例えば、&lt;br&gt;&lt;b&gt;\"ai\"&lt;/b&gt;は、次の動画を除外します:&lt;b&gt;How does AI work?&lt;/b&gt;&lt;br&gt;しかし、次の動画は除外しません:&lt;b&gt;What does fair use mean?&lt;/b&gt;</string>
@@ -797,6 +797,11 @@ MicroG GmsCore に対する電池の最適化を無効にしても、バッテ
設定 → 再生 → 次の動画を自動再生"</string>
<string name="revanced_end_screen_suggested_video_summary_off">「関連動画」は、再生終了時にプレーヤー画面に表示されます</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">フルスクリーンで関連動画のオーバーレイを非表示にする</string>
<string name="revanced_hide_related_video_overlay_summary_on">関連動画のオーバーレイは非表示になっています</string>
<string name="revanced_hide_related_video_overlay_summary_off">関連動画のオーバーレイが表示されています</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">タイムスタンプを非表示</string>
<string name="revanced_hide_timestamp_summary_on">タイムスタンプは表示されません</string>
@@ -1099,11 +1104,11 @@ MicroG GmsCore に対する電池の最適化を無効にしても、バッテ
<string name="revanced_change_form_factor_user_dialog_message">"変更点は以下のとおりです:
タブレット レイアウト
• コミュニティ投稿が表示されません
• コミュニティ投稿が表示されない
Automotive レイアウト
• ショート動画を通常のプレーヤーで開きます
• フィードがトピックとチャンネルで分類されます"</string>
• ショート動画を通常のプレーヤーで開
• フィードがトピックとチャンネルで分類され"</string>
</patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch">
<string name="revanced_spoof_app_version_title">アプリのバージョンを偽装する</string>
@@ -1189,8 +1194,8 @@ Automotive レイアウト
<string name="revanced_miniplayer_double_tap_action_title">「ダブルタップとピンチでサイズ変更」を有効にする</string>
<string name="revanced_miniplayer_double_tap_action_summary_on">"「ダブルタップとピンチでサイズ変更」は有効です
• ダブルタップミニプレーヤーのサイズ大きくします
• もう一度ダブルタップすると、元のサイズに戻ります"</string>
• ダブルタップすると、ミニプレーヤーのサイズ大きくなる
• もう一度ダブルタップすると、元のサイズに戻"</string>
<string name="revanced_miniplayer_double_tap_action_summary_off">「 ダブルタップとピンチでサイズ変更」は無効です</string>
<string name="revanced_miniplayer_drag_and_drop_title">ドラッグ&ドロップを有効にする</string>
<string name="revanced_miniplayer_drag_and_drop_summary_on">"ドラッグ&ドロップは有効です
@@ -1399,13 +1404,13 @@ Automotive レイアウト
</patch>
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
<string name="revanced_spoof_video_streams_screen_title">動画ストリームを偽装する</string>
<string name="revanced_spoof_video_streams_screen_summary">再生不能問題を回避するために、クライアントの動画ストリームを偽装します</string>
<string name="revanced_spoof_video_streams_screen_summary">動画の再生に失敗しないために、クライアントの動画ストリームを偽装します</string>
<string name="revanced_spoof_video_streams_title">動画ストリームを偽装する</string>
<string name="revanced_spoof_video_streams_summary_on">動画ストリームは偽装されます</string>
<string name="revanced_spoof_video_streams_summary_off">"動画ストリームは偽装されません
動画再生されない可能性があります"</string>
<string name="revanced_spoof_video_streams_user_dialog_message">この設定をオフにすると、動画再生されなくなる可能性があります。</string>
動画再生に失敗する可能性があります"</string>
<string name="revanced_spoof_video_streams_user_dialog_message">この設定をオフにすると、動画再生に失敗するようになる可能性があります。</string>
<string name="revanced_spoof_video_streams_client_type_title">デフォルトのクライアント</string>
<string name="revanced_spoof_video_streams_ios_force_avc_title">iOS クライアントで AVC (H.264) を強制的に使用する</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">ビデオ コーデックは強制的に AVC (H.264) が使用されます</string>
@@ -1414,15 +1419,15 @@ Automotive レイアウト
AVC は、最大解像度が 1080p であり、Opus オーディオ コーデックが利用できず、動画再生時の通信量が VP9 や AV1 より多くなります。"</string>
<string name="revanced_spoof_video_streams_about_ios_tv_title">iOS クライアントの副作用</string>
<string name="revanced_spoof_video_streams_about_ios_tv_summary">"• 映画や有料動画が再生されない可能性があります
•「 一定音量」が利用できません
• 動画が 1 秒早く終了します"</string>
<string name="revanced_spoof_video_streams_about_ios_tv_summary">"• 映画や有料動画が再生されない可能性があ
•「 一定音量」が利用できない
• 動画が 1 秒早く終了す"</string>
<string name="revanced_spoof_video_streams_about_android_title">Android クライアントの副作用</string>
<string name="revanced_spoof_video_streams_about_android_summary">"• 「音声トラック」がフライアウト メニューに表示されません
• 「一定音量」が利用できません
• 「デフォルトの吹き替えを無効にする」が利用できません"</string>
<string name="revanced_spoof_video_streams_about_no_av1">• AV1 コーデックが利用できません</string>
<string name="revanced_spoof_video_streams_about_kids_videos">• ログアウト時またはシークレット モード時に、子ども向け動画が再生されない可能性があります</string>
<string name="revanced_spoof_video_streams_about_android_summary">"• 「音声トラック」がフライアウト メニューに表示されない
• 「一定音量」が利用できない
• 「デフォルトの吹き替えを無効にする」が利用できない"</string>
<string name="revanced_spoof_video_streams_about_no_av1">• AV1 コーデックが利用できない</string>
<string name="revanced_spoof_video_streams_about_kids_videos">• ログアウト時またはシークレット モード時に、子ども向け動画が再生されない可能性があ</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">統計情報に表示する</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_on">現在のクライアントが統計情報に表示されます</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_off">現在のクライアントは統計情報に表示されません</string>

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -55,7 +55,7 @@ Second \"item\" text"</string>
<!-- Settings about dialog. -->
<string name="revanced_settings_about_links_body">ReVanced Patches &lt;i&gt;%s&lt;/i&gt;&lt;br&gt;버전을 사용 중입니다</string>
<string name="revanced_settings_about_links_dev_header">알림</string>
<string name="revanced_settings_about_links_dev_body">개발자 버전이므로&lt;br&gt;알려지지 않은 문제점이&lt;br&gt;발생할 수 있습니다</string>
<string name="revanced_settings_about_links_dev_body">Pre-Release 버전이므로&lt;br&gt;알려지지 않은 문제점이&lt;br&gt;발생할 수 있습니다</string>
<string name="revanced_settings_about_links_header">공식 링크</string>
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
@@ -794,6 +794,11 @@ MicroG 앱 배터리 최적화를 비활성화(제한 없음)하더라도, 배
설정 → 재생 → 다음 동영상 자동재생"</string>
<string name="revanced_end_screen_suggested_video_summary_off">최종 화면에서 다음 재생 추천 동영상이 표시됩니다</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">전체 화면에서 관련 동영상 오버레이 숨기기</string>
<string name="revanced_hide_related_video_overlay_summary_on">관련 동영상 오버레이가 숨겨집니다</string>
<string name="revanced_hide_related_video_overlay_summary_off">관련 동영상 오버레이가 표시됩니다</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">동영상 타임스탬프 숨기기</string>
<string name="revanced_hide_timestamp_summary_on">타임스탬프가 숨겨집니다</string>
@@ -975,7 +980,7 @@ MicroG 앱 배터리 최적화를 비활성화(제한 없음)하더라도, 배
<string name="revanced_sb_segments_filler">주제와 관련 없는 구간</string>
<string name="revanced_sb_segments_filler_sum">전반적인 동영상의 주제를 이해하는 데 필요 없는 내용을 포함하고 있습니다</string>
<string name="revanced_sb_segments_nomusic">음악이 아닌 구간</string>
<string name="revanced_sb_segments_nomusic_sum">뮤직 비디오에서 음악이 아닌 구간이 해당됩니다</string>
<string name="revanced_sb_segments_nomusic_sum">음악 동영상에서 음악이 아닌 구간이 해당됩니다</string>
<string name="revanced_sb_skip_button_compact">건너뛰기</string>
<string name="revanced_sb_skip_button_compact_highlight">하이라이트</string>
<string name="revanced_sb_skip_button_sponsor">스폰서 광고 건너뛰기</string>
@@ -1035,7 +1040,7 @@ MicroG 앱 배터리 최적화를 비활성화(제한 없음)하더라도, 배
<string name="revanced_sb_vote_segment_time_to_from">%1$s ~ %2$s</string>
<string name="revanced_sb_new_segment_choose_category">구간 카테고리를 선택하세요</string>
<string name="revanced_sb_new_segment_disabled_category">이 카테고리는 비활성화되어 있습니다. 제출하려면 설정에서 활성화해야 합니다.</string>
<string name="revanced_sb_new_segment_title">새 SponsorBlock 구간</string>
<string name="revanced_sb_new_segment_title">로운 SponsorBlock 구간</string>
<string name="revanced_sb_new_segment_mark_time_as_question">%s 을 구간의 시작 또는 끝으로 설정하시겠습니까?</string>
<string name="revanced_sb_new_segment_mark_start">시작</string>
<string name="revanced_sb_new_segment_mark_end"></string>

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -792,6 +792,11 @@ Automatinį paleidimą galima pakeisti „YouTube“ nustatymuose:
Nustatymai → Atkūrimas → Automatiškai leisti kitą vaizdo įrašą"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Pabaigos ekrane siūlomas vaizdo įrašas rodomas</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Slėpti susijusio vaizdo įrašo perdangą per visą ekraną</string>
<string name="revanced_hide_related_video_overlay_summary_on">Susijusio vaizdo įrašo perdanga paslėpta</string>
<string name="revanced_hide_related_video_overlay_summary_off">Rodoma susijusio vaizdo įrašo perdanga</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Slėpti vaizdo įrašo laiko žymę</string>
<string name="revanced_hide_timestamp_summary_on">Laiko žymė paslėpta</string>

View File

@@ -794,6 +794,11 @@ Automātisko atskaņošanu var mainīt YouTube iestatījumos:
Iestatījumi → Atskaņošana → Automātiski atskaņot nākamo video"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Ieteiktais video beigu ekrānā ir redzams</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_video_overlay_title">Slēpt saistīto video pārklājumu pilnekrāna režīmā</string>
<string name="revanced_hide_related_video_overlay_summary_on">Saistīto video pārklājums ir paslēpts</string>
<string name="revanced_hide_related_video_overlay_summary_off">Saistīto video pārklājums ir redzams</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Paslēpt video laika zīmi</string>
<string name="revanced_hide_timestamp_summary_on">Laika zīme ir paslēpta</string>

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

View File

@@ -134,6 +134,8 @@ Second \"item\" text"</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
</patch>
<patch id="layout.panels.popup.playerPopupPanelsPatch">

Some files were not shown because too many files have changed in this diff Show More