mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-10 11:23:57 +01:00
Compare commits
315 Commits
v5.12.0-de
...
v5.24.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4f9cb3cef | ||
|
|
9aec1999bb | ||
|
|
26ecbe646e | ||
|
|
46ba0d8a2e | ||
|
|
f454183646 | ||
|
|
d2b440d800 | ||
|
|
494c5f04a4 | ||
|
|
48d5fdf7e1 | ||
|
|
887c9f0d75 | ||
|
|
7de4c9d41d | ||
|
|
7d3b8d9c42 | ||
|
|
25e1a965d6 | ||
|
|
b29c01cee1 | ||
|
|
639850471b | ||
|
|
796c118fe1 | ||
|
|
edf20e397d | ||
|
|
5f0541407c | ||
|
|
56b7ba9ba7 | ||
|
|
f8bdf744ab | ||
|
|
f4f36ff273 | ||
|
|
5028c1acb3 | ||
|
|
555c9a5823 | ||
|
|
777957e2d0 | ||
|
|
b3316a5915 | ||
|
|
2ca2bb7692 | ||
|
|
23fd720fa7 | ||
|
|
1f08586ae8 | ||
|
|
60fdf4c44c | ||
|
|
63f3342815 | ||
|
|
858c59d728 | ||
|
|
5debf9936d | ||
|
|
f1b85d20a1 | ||
|
|
37d0de5e93 | ||
|
|
96d08d5eb7 | ||
|
|
9b1013e1c2 | ||
|
|
75d6cd7c7b | ||
|
|
5a17f5e1c1 | ||
|
|
1d16de6617 | ||
|
|
aee7cba46d | ||
|
|
ec3faf30a8 | ||
|
|
45b5a51da3 | ||
|
|
8abf176bc9 | ||
|
|
ef35ed7335 | ||
|
|
4fd666b667 | ||
|
|
72e0c01922 | ||
|
|
f69eab3e3b | ||
|
|
7c5c2d95bc | ||
|
|
b2453fecfc | ||
|
|
0d54f8bd80 | ||
|
|
fda16fad1a | ||
|
|
ddd43acd73 | ||
|
|
3451318d53 | ||
|
|
2d94ba9df6 | ||
|
|
aaf3437a5a | ||
|
|
ec8bf06047 | ||
|
|
96512de6c9 | ||
|
|
6114807c43 | ||
|
|
6d69f01421 | ||
|
|
fd4218154d | ||
|
|
8bed8a6622 | ||
|
|
3174047223 | ||
|
|
15053e2b68 | ||
|
|
e5b6aac018 | ||
|
|
d7c9dd0f77 | ||
|
|
a0eb6d5fdb | ||
|
|
55c5eb3d14 | ||
|
|
896de8910a | ||
|
|
e2a7e25c66 | ||
|
|
77ea5c4033 | ||
|
|
6eea2354f5 | ||
|
|
cce21c4d4a | ||
|
|
5e069bde90 | ||
|
|
6a49208982 | ||
|
|
404bb2e86e | ||
|
|
bc869fe359 | ||
|
|
7d166cf82c | ||
|
|
8efbaae65c | ||
|
|
e27ab23279 | ||
|
|
ce42604083 | ||
|
|
fc6282d0cb | ||
|
|
0559fc7fd0 | ||
|
|
7cc6995682 | ||
|
|
476f13bf98 | ||
|
|
f216e16c0b | ||
|
|
f2a8789649 | ||
|
|
5973b64f52 | ||
|
|
102036706e | ||
|
|
2393d0a8f5 | ||
|
|
aea29b9522 | ||
|
|
4db8ef7079 | ||
|
|
7fbd26ccad | ||
|
|
91995ea01d | ||
|
|
86f867fe97 | ||
|
|
0f687ecfd3 | ||
|
|
6c8b7d09c1 | ||
|
|
3d6958f157 | ||
|
|
43d7cc7374 | ||
|
|
5ebd449f1f | ||
|
|
346a061df8 | ||
|
|
13e490a422 | ||
|
|
b4e8540bbc | ||
|
|
775c1baec2 | ||
|
|
9419fb8ec4 | ||
|
|
c510931eb0 | ||
|
|
7160699384 | ||
|
|
9db67a6eb2 | ||
|
|
e684d87dd3 | ||
|
|
2d1752a1eb | ||
|
|
c9ff7092fe | ||
|
|
d451bc6d6d | ||
|
|
741fd36872 | ||
|
|
517f8cf59a | ||
|
|
b78fb24435 | ||
|
|
a3faccb21b | ||
|
|
5f0fddc122 | ||
|
|
854a18ff72 | ||
|
|
b994a16bdc | ||
|
|
f68d06dbf3 | ||
|
|
04c6a2e5f4 | ||
|
|
e6ae55fa99 | ||
|
|
fb62474ff4 | ||
|
|
e084f01fd0 | ||
|
|
d573386e0f | ||
|
|
0f3aeb35e5 | ||
|
|
e30f593af0 | ||
|
|
df965b8a9b | ||
|
|
654587a75e | ||
|
|
9956833781 | ||
|
|
c585b26188 | ||
|
|
de0d11fcfb | ||
|
|
d321504fcf | ||
|
|
6005c97bf5 | ||
|
|
e404d84c83 | ||
|
|
1abed31968 | ||
|
|
a75a88d3c6 | ||
|
|
3d67d90473 | ||
|
|
fa1e137a43 | ||
|
|
ac71a53c73 | ||
|
|
0bff207efc | ||
|
|
e1a8b388a5 | ||
|
|
628d18489c | ||
|
|
36772b8b2e | ||
|
|
49c849979f | ||
|
|
0bdb8cdf2b | ||
|
|
2035c9e2e9 | ||
|
|
7cb38fd3fc | ||
|
|
8ed9d5bf08 | ||
|
|
cd467d6244 | ||
|
|
fdefb67d02 | ||
|
|
5274cd18f0 | ||
|
|
3d68c06146 | ||
|
|
ef3d5bafd5 | ||
|
|
2d7b1b09af | ||
|
|
0572d48fde | ||
|
|
37984b8b99 | ||
|
|
6e63193f06 | ||
|
|
b2384b22a5 | ||
|
|
ccb76983ff | ||
|
|
318b55b8fe | ||
|
|
49ade9efbc | ||
|
|
d77515bd68 | ||
|
|
087bf1e152 | ||
|
|
c2994d583d | ||
|
|
127b0a63fe | ||
|
|
27aafd0ee1 | ||
|
|
49c54c0e54 | ||
|
|
842ba4fc4d | ||
|
|
66ecadce4f | ||
|
|
73ca04da5e | ||
|
|
a5d26208c1 | ||
|
|
497291c478 | ||
|
|
b24278a544 | ||
|
|
135f9ead3c | ||
|
|
ca4f960171 | ||
|
|
7f228cc535 | ||
|
|
bf91e127d8 | ||
|
|
f07fc1ad93 | ||
|
|
c84be120bd | ||
|
|
e67f390e2b | ||
|
|
4d910fea93 | ||
|
|
72adbe5519 | ||
|
|
54d49b774e | ||
|
|
283bb31567 | ||
|
|
2724fcbd27 | ||
|
|
7c28193579 | ||
|
|
cd1ee814c4 | ||
|
|
d9ccd73b5f | ||
|
|
5c5a1e4b8b | ||
|
|
66a2ee2416 | ||
|
|
d8c276cf96 | ||
|
|
d5845abd08 | ||
|
|
54eef22ce7 | ||
|
|
e287bdc59d | ||
|
|
20a82ef956 | ||
|
|
1e29da9e06 | ||
|
|
56e6a90a90 | ||
|
|
76d32e21c2 | ||
|
|
54a7afa540 | ||
|
|
ef86438bac | ||
|
|
0683cedac0 | ||
|
|
35753410aa | ||
|
|
df838ed91d | ||
|
|
8e494d26d4 | ||
|
|
7d834e5421 | ||
|
|
60a31cf4e1 | ||
|
|
edb8bd66bc | ||
|
|
04a170054e | ||
|
|
79e6349a69 | ||
|
|
bbf3a34a2f | ||
|
|
1db7c49514 | ||
|
|
ef0506a4f8 | ||
|
|
9b38da35ff | ||
|
|
afdb771066 | ||
|
|
1b2b536d2e | ||
|
|
f39e70c648 | ||
|
|
556acdd9c1 | ||
|
|
7adfc637dc | ||
|
|
9cc0c075ad | ||
|
|
ead11e7f46 | ||
|
|
e9bc201641 | ||
|
|
99baedf355 | ||
|
|
0338d0acd3 | ||
|
|
99879f6e0a | ||
|
|
f0c70de602 | ||
|
|
737ae07a06 | ||
|
|
2c51de59de | ||
|
|
df3dc1c0b2 | ||
|
|
074c948581 | ||
|
|
2a88b1f895 | ||
|
|
ee5c830df8 | ||
|
|
e63a4b31f3 | ||
|
|
8d0bca3b03 | ||
|
|
c162d65d5b | ||
|
|
67dcd091c4 | ||
|
|
ac5ce2d67f | ||
|
|
4b78d056fd | ||
|
|
f8c901b2c1 | ||
|
|
2a67c312e1 | ||
|
|
a7eed30f46 | ||
|
|
e2de2d8d44 | ||
|
|
7ebbf356c0 | ||
|
|
2ced5c6e2a | ||
|
|
4a090ba659 | ||
|
|
cb609a6d9d | ||
|
|
42e6de9e8f | ||
|
|
c4a5b9a28c | ||
|
|
c86c85947f | ||
|
|
cbbf474c50 | ||
|
|
f147b7b73d | ||
|
|
fb8dbb4723 | ||
|
|
1e0d27e689 | ||
|
|
a2185bce09 | ||
|
|
1b60a72ede | ||
|
|
12b4ee04ad | ||
|
|
f9a6cc96de | ||
|
|
93ea250bf3 | ||
|
|
fdb946a2cc | ||
|
|
7cc939ab03 | ||
|
|
228d72428d | ||
|
|
4db7ab4207 | ||
|
|
329f993024 | ||
|
|
7cd1fb22d8 | ||
|
|
ae111bc0b9 | ||
|
|
79f1dfd3e8 | ||
|
|
f5dd902915 | ||
|
|
10e2b08eb2 | ||
|
|
4ae1155e51 | ||
|
|
69fbfaea19 | ||
|
|
f44fede67c | ||
|
|
3c52ab8017 | ||
|
|
d1641a6e3d | ||
|
|
09773e8934 | ||
|
|
d77d5bfbdd | ||
|
|
a84bded9e7 | ||
|
|
e664a24f73 | ||
|
|
5bf964fff6 | ||
|
|
0c0bbb8713 | ||
|
|
8afe48cd92 | ||
|
|
dde8ea31cb | ||
|
|
d3abbe3e93 | ||
|
|
c8179776ed | ||
|
|
c6c6516b12 | ||
|
|
d6eae01e12 | ||
|
|
ba88603f4b | ||
|
|
d5aab3d464 | ||
|
|
fca2f70c0e | ||
|
|
348f7e12cb | ||
|
|
b6b7208eeb | ||
|
|
a2c79f1349 | ||
|
|
4f5bb3c915 | ||
|
|
4b77d27c77 | ||
|
|
7991c80129 | ||
|
|
6baf4ea2ac | ||
|
|
c89538c8f5 | ||
|
|
94fb367618 | ||
|
|
354835966d | ||
|
|
168f9b769e | ||
|
|
e4c4b3a73a | ||
|
|
fce98b4960 | ||
|
|
839aa81e9c | ||
|
|
905bb0ea5f | ||
|
|
a94a663859 | ||
|
|
04b37dd55a | ||
|
|
2382e9d09e | ||
|
|
97f504976a | ||
|
|
0a6c5158e0 | ||
|
|
a959d798e8 | ||
|
|
39a0b9bda6 | ||
|
|
92c38b2cb4 | ||
|
|
4732210d4b | ||
|
|
f30a49f1cb | ||
|
|
bcd157dd2b | ||
|
|
d299ea5973 | ||
|
|
a20021e290 | ||
|
|
373ca966f3 |
2
.github/workflows/pull_strings.yml
vendored
2
.github/workflows/pull_strings.yml
vendored
@@ -2,7 +2,7 @@ name: Pull strings
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 */6 * * *"
|
- cron: "0 */12 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
1007
CHANGELOG.md
1007
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
16
extensions/all/misc/adb/hide-adb/build.gradle.kts
Normal file
16
extensions/all/misc/adb/hide-adb/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly(libs.annotation)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package app.revanced.extension.all.misc.hide.adb;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class HideAdbPatch {
|
||||||
|
private static final List<String> SPOOF_SETTINGS = Arrays.asList("adb_enabled", "adb_wifi_enabled", "development_settings_enabled");
|
||||||
|
|
||||||
|
public static int getInt(ContentResolver cr, String name) throws Settings.SettingNotFoundException {
|
||||||
|
if (SPOOF_SETTINGS.contains(name)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Settings.Global.getInt(cr, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getInt(ContentResolver cr, String name, int def) {
|
||||||
|
if (SPOOF_SETTINGS.contains(name)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Settings.Global.getInt(cr, name, def);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.extension.all.connectivity.wifi.spoof;
|
package app.revanced.extension.all.misc.connectivity.wifi.spoof;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.extension.all.screencapture.removerestriction;
|
package app.revanced.extension.all.misc.screencapture.removerestriction;
|
||||||
|
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.extension.all.screenshot.removerestriction;
|
package app.revanced.extension.all.misc.screenshot.removerestriction;
|
||||||
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
4
extensions/nunl/build.gradle.kts
Normal file
4
extensions/nunl/build.gradle.kts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:shared:library"))
|
||||||
|
compileOnly(project(":extensions:nunl:stub"))
|
||||||
|
}
|
||||||
1
extensions/nunl/src/main/AndroidManifest.xml
Normal file
1
extensions/nunl/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package app.revanced.extension.nunl.ads;
|
||||||
|
|
||||||
|
import nl.nu.performance.api.client.interfaces.Block;
|
||||||
|
import nl.nu.performance.api.client.unions.SmallArticleLinkFlavor;
|
||||||
|
import nl.nu.performance.api.client.objects.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class HideAdsPatch {
|
||||||
|
private static final String[] blockedHeaderBlocks = {
|
||||||
|
"Aanbiedingen (Adverteerders)",
|
||||||
|
"Aangeboden door NUshop"
|
||||||
|
};
|
||||||
|
|
||||||
|
// "Rubrieken" menu links to ads.
|
||||||
|
private static final String[] blockedLinkBlocks = {
|
||||||
|
"Van onze adverteerders"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void filterAds(List<Block> blocks) {
|
||||||
|
try {
|
||||||
|
ArrayList<Block> cleanedList = new ArrayList<>();
|
||||||
|
|
||||||
|
boolean skipFullHeader = false;
|
||||||
|
boolean skipUntilDivider = false;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
while (index < blocks.size()) {
|
||||||
|
Block currentBlock = blocks.get(index);
|
||||||
|
|
||||||
|
// Because of pagination, we might not see the Divider in front of it.
|
||||||
|
// Just remove it as is and leave potential extra spacing visible on the screen.
|
||||||
|
if (currentBlock instanceof DpgBannerBlock) {
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index + 1 < blocks.size()) {
|
||||||
|
// Filter Divider -> DpgMediaBanner -> Divider.
|
||||||
|
if (currentBlock instanceof DividerBlock
|
||||||
|
&& blocks.get(index + 1) instanceof DpgBannerBlock) {
|
||||||
|
index += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter Divider -> LinkBlock (... -> LinkBlock -> LinkBlock-> LinkBlock -> Divider).
|
||||||
|
if (currentBlock instanceof DividerBlock
|
||||||
|
&& blocks.get(index + 1) instanceof LinkBlock linkBlock) {
|
||||||
|
Link link = linkBlock.getLink();
|
||||||
|
if (link != null && link.getTitle() != null) {
|
||||||
|
for (String blockedLinkBlock : blockedLinkBlocks) {
|
||||||
|
if (blockedLinkBlock.equals(link.getTitle().getText())) {
|
||||||
|
skipUntilDivider = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (skipUntilDivider) {
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip LinkBlocks with a "flavor" claiming to be "isPartner" (sponsored inline ads).
|
||||||
|
if (currentBlock instanceof LinkBlock linkBlock
|
||||||
|
&& linkBlock.getLink() != null
|
||||||
|
&& linkBlock.getLink().getLinkFlavor() instanceof SmallArticleLinkFlavor smallArticleLinkFlavor
|
||||||
|
&& smallArticleLinkFlavor.isPartner() != null
|
||||||
|
&& smallArticleLinkFlavor.isPartner()) {
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBlock instanceof DividerBlock) {
|
||||||
|
skipUntilDivider = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter HeaderBlock with known ads until next HeaderBlock.
|
||||||
|
if (currentBlock instanceof HeaderBlock headerBlock) {
|
||||||
|
StyledText headerText = headerBlock.getTitle();
|
||||||
|
if (headerText != null) {
|
||||||
|
skipFullHeader = false;
|
||||||
|
for (String blockedHeaderBlock : blockedHeaderBlocks) {
|
||||||
|
if (blockedHeaderBlock.equals(headerText.getText())) {
|
||||||
|
skipFullHeader = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (skipFullHeader) {
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skipFullHeader && !skipUntilDivider) {
|
||||||
|
cleanedList.add(currentBlock);
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace list in-place to not deal with moving the result to the correct register in smali.
|
||||||
|
blocks.clear();
|
||||||
|
blocks.addAll(cleanedList);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "filterAds failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
extensions/nunl/stub/build.gradle.kts
Normal file
17
extensions/nunl/stub/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
plugins {
|
||||||
|
id(libs.plugins.android.library.get().pluginId)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
}
|
||||||
1
extensions/nunl/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/nunl/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package nl.nu.performance.api.client.interfaces;
|
||||||
|
|
||||||
|
public class Block {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package nl.nu.performance.api.client.objects;
|
||||||
|
|
||||||
|
import nl.nu.performance.api.client.interfaces.Block;
|
||||||
|
|
||||||
|
public class DividerBlock extends Block {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package nl.nu.performance.api.client.objects;
|
||||||
|
|
||||||
|
import nl.nu.performance.api.client.interfaces.Block;
|
||||||
|
|
||||||
|
public class DpgBannerBlock extends Block {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package nl.nu.performance.api.client.objects;
|
||||||
|
|
||||||
|
import nl.nu.performance.api.client.interfaces.Block;
|
||||||
|
|
||||||
|
public class HeaderBlock extends Block {
|
||||||
|
public final StyledText getTitle() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package nl.nu.performance.api.client.objects;
|
||||||
|
|
||||||
|
import nl.nu.performance.api.client.unions.LinkFlavor;
|
||||||
|
|
||||||
|
public class Link {
|
||||||
|
public final StyledText getTitle() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
public final LinkFlavor getLinkFlavor() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package nl.nu.performance.api.client.objects;
|
||||||
|
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import nl.nu.performance.api.client.interfaces.Block;
|
||||||
|
|
||||||
|
public abstract class LinkBlock extends Block implements Parcelable {
|
||||||
|
public final Link getLink() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package nl.nu.performance.api.client.objects;
|
||||||
|
|
||||||
|
public class StyledText {
|
||||||
|
public final String getText() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package nl.nu.performance.api.client.unions;
|
||||||
|
|
||||||
|
public interface LinkFlavor {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package nl.nu.performance.api.client.unions;
|
||||||
|
|
||||||
|
public class SmallArticleLinkFlavor implements LinkFlavor {
|
||||||
|
public final Boolean isPartner() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
}
|
||||||
4
extensions/primevideo/build.gradle.kts
Normal file
4
extensions/primevideo/build.gradle.kts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:shared:library"))
|
||||||
|
compileOnly(project(":extensions:primevideo:stub"))
|
||||||
|
}
|
||||||
1
extensions/primevideo/src/main/AndroidManifest.xml
Normal file
1
extensions/primevideo/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package app.revanced.extension.primevideo.ads;
|
||||||
|
|
||||||
|
import com.amazon.avod.fsm.SimpleTrigger;
|
||||||
|
import com.amazon.avod.media.ads.AdBreak;
|
||||||
|
import com.amazon.avod.media.ads.internal.state.AdBreakTrigger;
|
||||||
|
import com.amazon.avod.media.ads.internal.state.AdEnabledPlayerTriggerType;
|
||||||
|
import com.amazon.avod.media.playback.VideoPlayer;
|
||||||
|
import com.amazon.avod.media.ads.internal.state.ServerInsertedAdBreakState;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class SkipAdsPatch {
|
||||||
|
public static void enterServerInsertedAdBreakState(ServerInsertedAdBreakState state, AdBreakTrigger trigger, VideoPlayer player) {
|
||||||
|
try {
|
||||||
|
AdBreak adBreak = trigger.getBreak();
|
||||||
|
|
||||||
|
// There are two scenarios when entering the original method:
|
||||||
|
// 1. Player naturally entered an ad break while watching a video.
|
||||||
|
// 2. User is skipped/scrubbed to a position on the timeline. If seek position is past an ad break,
|
||||||
|
// user is forced to watch an ad before continuing.
|
||||||
|
//
|
||||||
|
// Scenario 2 is indicated by trigger.getSeekStartPosition() != null, so skip directly to the scrubbing
|
||||||
|
// target. Otherwise, just calculate when the ad break should end and skip to there.
|
||||||
|
if (trigger.getSeekStartPosition() != null)
|
||||||
|
player.seekTo(trigger.getSeekTarget().getTotalMilliseconds());
|
||||||
|
else
|
||||||
|
player.seekTo(player.getCurrentPosition() + adBreak.getDurationExcludingAux().getTotalMilliseconds());
|
||||||
|
|
||||||
|
// Send "end of ads" trigger to state machine so everything doesn't get whacky.
|
||||||
|
state.doTrigger(new SimpleTrigger(AdEnabledPlayerTriggerType.NO_MORE_ADS_SKIP_TRANSITION));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Failed skipping ads", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
extensions/primevideo/stub/build.gradle.kts
Normal file
17
extensions/primevideo/stub/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
plugins {
|
||||||
|
id(libs.plugins.android.library.get().pluginId)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
1
extensions/primevideo/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/primevideo/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.amazon.avod.fsm;
|
||||||
|
|
||||||
|
public final class SimpleTrigger<T> implements Trigger<T> {
|
||||||
|
public SimpleTrigger(T triggerType) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.amazon.avod.fsm;
|
||||||
|
|
||||||
|
public abstract class StateBase<S, T> {
|
||||||
|
// This method orginally has protected access (modified in patch code).
|
||||||
|
public void doTrigger(Trigger<T> trigger) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.amazon.avod.fsm;
|
||||||
|
|
||||||
|
public interface Trigger<T> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.amazon.avod.media;
|
||||||
|
|
||||||
|
public final class TimeSpan {
|
||||||
|
public long getTotalMilliseconds() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.amazon.avod.media.ads;
|
||||||
|
|
||||||
|
import com.amazon.avod.media.TimeSpan;
|
||||||
|
|
||||||
|
public interface AdBreak {
|
||||||
|
TimeSpan getDurationExcludingAux();
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
public abstract class AdBreakState extends AdEnabledPlaybackState {
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
import com.amazon.avod.media.ads.AdBreak;
|
||||||
|
import com.amazon.avod.media.TimeSpan;
|
||||||
|
|
||||||
|
public class AdBreakTrigger {
|
||||||
|
public AdBreak getBreak() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan getSeekTarget() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan getSeekStartPosition() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
import com.amazon.avod.fsm.StateBase;
|
||||||
|
import com.amazon.avod.media.playback.state.PlayerStateType;
|
||||||
|
import com.amazon.avod.media.playback.state.trigger.PlayerTriggerType;
|
||||||
|
|
||||||
|
public class AdEnabledPlaybackState extends StateBase<PlayerStateType, PlayerTriggerType> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
public enum AdEnabledPlayerTriggerType {
|
||||||
|
NO_MORE_ADS_SKIP_TRANSITION
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
public class ServerInsertedAdBreakState extends AdBreakState {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.amazon.avod.media.playback;
|
||||||
|
|
||||||
|
public interface VideoPlayer {
|
||||||
|
long getCurrentPosition();
|
||||||
|
|
||||||
|
void seekTo(long positionMs);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.amazon.avod.media.playback.state;
|
||||||
|
|
||||||
|
public interface PlayerStateType {
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.amazon.avod.media.playback.state.trigger;
|
||||||
|
|
||||||
|
public interface PlayerTriggerType {
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.revanced.extension.shared;
|
package app.revanced.extension.shared;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -15,14 +16,18 @@ import android.os.Build;
|
|||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
import app.revanced.extension.shared.requests.Requester;
|
||||||
* @noinspection unused
|
import app.revanced.extension.shared.requests.Route;
|
||||||
*/
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class GmsCoreSupport {
|
public class GmsCoreSupport {
|
||||||
private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube";
|
private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube";
|
||||||
private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music";
|
private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music";
|
||||||
@@ -31,10 +36,24 @@ public class GmsCoreSupport {
|
|||||||
= getGmsCoreVendorGroupId() + ".android.gms";
|
= getGmsCoreVendorGroupId() + ".android.gms";
|
||||||
private static final Uri GMS_CORE_PROVIDER
|
private static final Uri GMS_CORE_PROVIDER
|
||||||
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
|
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
|
||||||
private static final String DONT_KILL_MY_APP_LINK
|
private static final String DONT_KILL_MY_APP_URL
|
||||||
= "https://dontkillmyapp.com";
|
= "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) {
|
private static void open(String queryOrLink) {
|
||||||
|
Logger.printInfo(() -> "Opening link: " + queryOrLink);
|
||||||
|
|
||||||
Intent intent;
|
Intent intent;
|
||||||
try {
|
try {
|
||||||
// Check if queryOrLink is a valid URL.
|
// Check if queryOrLink is a valid URL.
|
||||||
@@ -88,7 +107,7 @@ public class GmsCoreSupport {
|
|||||||
|
|
||||||
// Do not exit. If the app exits before launch completes (and without
|
// 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
|
// 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.
|
// with the appearance of a hung app.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,11 +143,12 @@ public class GmsCoreSupport {
|
|||||||
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
|
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||||
|
checkIfDontKillMyAppSupportsManufacturer();
|
||||||
|
|
||||||
showBatteryOptimizationDialog(context,
|
showBatteryOptimizationDialog(context,
|
||||||
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
||||||
"gms_core_dialog_open_website_text",
|
"gms_core_dialog_open_website_text",
|
||||||
(dialog, id) -> open(DONT_KILL_MY_APP_LINK));
|
(dialog, id) -> openDontKillMyApp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -143,6 +163,48 @@ public class GmsCoreSupport {
|
|||||||
activity.startActivityForResult(intent, 0);
|
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.
|
* @return If GmsCore is not whitelisted from battery optimizations.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.content.pm.PackageInfo;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -356,33 +357,24 @@ public class Utils {
|
|||||||
|
|
||||||
public static Context getContext() {
|
public static Context getContext() {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
Logger.initializationException(Utils.class, "Context is null, returning null!", null);
|
Logger.initializationException(Utils.class, "Context is not set by extension hook, returning null", null);
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setContext(Context appContext) {
|
public static void setContext(Context appContext) {
|
||||||
// Must initially set context as the language settings needs it.
|
// Must initially set context to check the app language.
|
||||||
context = appContext;
|
context = appContext;
|
||||||
|
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
|
||||||
|
|
||||||
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||||
if (language != AppLanguage.DEFAULT) {
|
if (language != AppLanguage.DEFAULT) {
|
||||||
// Create a new context with the desired language.
|
// Create a new context with the desired language.
|
||||||
|
Logger.printDebug(() -> "Using app language: " + language);
|
||||||
Configuration config = appContext.getResources().getConfiguration();
|
Configuration config = appContext.getResources().getConfiguration();
|
||||||
config.setLocale(language.getLocale());
|
config.setLocale(language.getLocale());
|
||||||
context = appContext.createConfigurationContext(config);
|
context = appContext.createConfigurationContext(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies.
|
|
||||||
// Calling the regular printDebug method here can cause a Settings context null pointer exception,
|
|
||||||
// even though the context is already set before the call.
|
|
||||||
//
|
|
||||||
// The initialization logger methods do not directly or indirectly
|
|
||||||
// reference the Context or any Settings and are unaffected by this problem.
|
|
||||||
//
|
|
||||||
// Info level also helps debug if a patch hook is called before
|
|
||||||
// the context is set since debug logging is off by default.
|
|
||||||
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setClipboard(@NonNull String text) {
|
public static void setClipboard(@NonNull String text) {
|
||||||
@@ -808,4 +800,14 @@ public class Utils {
|
|||||||
builder.getContext().setTheme(editTextDialogStyle);
|
builder.getContext().setTheme(editTextDialogStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a color resource or hex code to an int representation of the color.
|
||||||
|
*/
|
||||||
|
public static int getColorFromString(String colorString) throws IllegalArgumentException, Resources.NotFoundException {
|
||||||
|
if (colorString.startsWith("#")) {
|
||||||
|
return Color.parseColor(colorString);
|
||||||
|
}
|
||||||
|
return getResourceColor(colorString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ public enum AppLanguage {
|
|||||||
*/
|
*/
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
|
|
||||||
|
// Languages codes not included with YouTube, but are translated on Crowdin
|
||||||
|
GA,
|
||||||
|
|
||||||
// Language codes found in locale_config.xml
|
// Language codes found in locale_config.xml
|
||||||
// All region specific variants have been removed.
|
// All region specific variants have been removed.
|
||||||
AF,
|
AF,
|
||||||
|
|||||||
@@ -23,12 +23,17 @@ public class BaseSettings {
|
|||||||
|
|
||||||
public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true, "revanced_language_user_dialog_message");
|
public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true, "revanced_language_user_dialog_message");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the icons declared in the preferences created during patching. If no icons or styles are declared then this setting does nothing.
|
||||||
|
*/
|
||||||
|
public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true);
|
||||||
|
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||||
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
||||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
||||||
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
|
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
|
||||||
// Client type must be last spoof setting due to cyclic references.
|
// Client type must be last spoof setting due to cyclic references.
|
||||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
|
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_UNPLUGGED, true, parent(SPOOF_VIDEO_STREAMS));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ public class BooleanSetting extends Setting<Boolean> {
|
|||||||
*/
|
*/
|
||||||
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
||||||
setting.value = Objects.requireNonNull(newValue);
|
setting.value = Objects.requireNonNull(newValue);
|
||||||
|
|
||||||
|
if (setting.isSetToDefault()) {
|
||||||
|
setting.removeFromPreferences();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,10 +69,8 @@ public class BooleanSetting extends Setting<Boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull Boolean newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveBoolean(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveBoolean(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -89,10 +89,8 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull T newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveEnumAsString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveEnumAsString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ public class FloatSetting extends Setting<Float> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull Float newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveFloatString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveFloatString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ public class IntegerSetting extends Setting<Integer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull Integer newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveIntegerString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveIntegerString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ public class LongSetting extends Setting<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull Long newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveLongString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveLongString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import java.util.*;
|
|||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public abstract class Setting<T> {
|
public abstract class Setting<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -288,6 +287,13 @@ public abstract class Setting<T> {
|
|||||||
*/
|
*/
|
||||||
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) {
|
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) {
|
||||||
setting.setValueFromString(newValue);
|
setting.setValueFromString(newValue);
|
||||||
|
|
||||||
|
// Clear the preference value since default is used, to allow changing
|
||||||
|
// the changing the default for a future release. Without this after upgrading
|
||||||
|
// the saved value will be whatever was the default when the app was first installed.
|
||||||
|
if (setting.isSetToDefault()) {
|
||||||
|
setting.removeFromPreferences();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -303,16 +309,45 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* Persistently saves the value.
|
* Persistently saves the value.
|
||||||
*/
|
*/
|
||||||
public abstract void save(@NonNull T newValue);
|
public final void save(@NonNull T newValue) {
|
||||||
|
if (value.equals(newValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
||||||
|
value = Objects.requireNonNull(newValue);
|
||||||
|
|
||||||
|
if (defaultValue.equals(newValue)) {
|
||||||
|
removeFromPreferences();
|
||||||
|
} else {
|
||||||
|
saveToPreferences();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save {@link #value} to {@link #preferences}.
|
||||||
|
*/
|
||||||
|
protected abstract void saveToPreferences();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove {@link #value} from {@link #preferences}.
|
||||||
|
*/
|
||||||
|
protected final void removeFromPreferences() {
|
||||||
|
Logger.printDebug(() -> "Clearing stored preference value (reset to default): " + key);
|
||||||
|
preferences.removeKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public abstract T get();
|
public abstract T get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identical to calling {@link #save(Object)} using {@link #defaultValue}.
|
* Identical to calling {@link #save(Object)} using {@link #defaultValue}.
|
||||||
|
*
|
||||||
|
* @return The newly saved default value.
|
||||||
*/
|
*/
|
||||||
public void resetToDefault() {
|
public T resetToDefault() {
|
||||||
save(defaultValue);
|
save(defaultValue);
|
||||||
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ public class StringSetting extends Setting<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull String newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -22,12 +22,23 @@ import app.revanced.extension.shared.settings.Setting;
|
|||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that if a preference changes,
|
* Indicates that if a preference changes,
|
||||||
* to apply the change from the Setting to the UI component.
|
* to apply the change from the Setting to the UI component.
|
||||||
*/
|
*/
|
||||||
public static boolean settingImportInProgress;
|
public static boolean settingImportInProgress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents recursive calls during preference <-> UI syncing from showing extra dialogs.
|
||||||
|
*/
|
||||||
|
private static boolean updatingPreference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
|
||||||
|
*/
|
||||||
|
private static boolean showingUserDialogMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Confirm and restart dialog button text and title.
|
* Confirm and restart dialog button text and title.
|
||||||
* Set by subclasses if Strings cannot be added as a resource.
|
* Set by subclasses if Strings cannot be added as a resource.
|
||||||
@@ -35,13 +46,13 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
@Nullable
|
@Nullable
|
||||||
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle;
|
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle;
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
|
|
||||||
*/
|
|
||||||
private boolean showingUserDialogMessage;
|
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||||
try {
|
try {
|
||||||
|
if (updatingPreference) {
|
||||||
|
Logger.printDebug(() -> "Ignoring preference change as sync is in progress");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
|
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
|
||||||
if (setting == null) {
|
if (setting == null) {
|
||||||
return;
|
return;
|
||||||
@@ -63,16 +74,18 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatingPreference = true;
|
||||||
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
|
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
|
||||||
|
// Updating here can can cause a recursive call back into this same method.
|
||||||
updatePreference(pref, setting, true, settingImportInProgress);
|
updatePreference(pref, setting, true, settingImportInProgress);
|
||||||
// Update any other preference availability that may now be different.
|
// Update any other preference availability that may now be different.
|
||||||
updateUIAvailability();
|
updateUIAvailability();
|
||||||
|
updatingPreference = false;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize this instance, and do any custom behavior.
|
* Initialize this instance, and do any custom behavior.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -81,7 +94,10 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
* so all app specific {@link Setting} instances are loaded before this method returns.
|
* so all app specific {@link Setting} instances are loaded before this method returns.
|
||||||
*/
|
*/
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
final var identifier = Utils.getResourceIdentifier("revanced_prefs", "xml");
|
String preferenceResourceName = BaseSettings.SHOW_MENU_ICONS.get()
|
||||||
|
? "revanced_prefs_icons"
|
||||||
|
: "revanced_prefs";
|
||||||
|
final var identifier = Utils.getResourceIdentifier(preferenceResourceName, "xml");
|
||||||
if (identifier == 0) return;
|
if (identifier == 0) return;
|
||||||
addPreferencesFromResource(identifier);
|
addPreferencesFromResource(identifier);
|
||||||
|
|
||||||
@@ -97,7 +113,9 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
if (confirmDialogTitle == null) {
|
if (confirmDialogTitle == null) {
|
||||||
confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title");
|
confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title");
|
||||||
}
|
}
|
||||||
|
|
||||||
showingUserDialogMessage = true;
|
showingUserDialogMessage = true;
|
||||||
|
|
||||||
new AlertDialog.Builder(context)
|
new AlertDialog.Builder(context)
|
||||||
.setTitle(confirmDialogTitle)
|
.setTitle(confirmDialogTitle)
|
||||||
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
|
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
|
||||||
@@ -141,14 +159,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
* @return If the preference is currently set to the default value of the Setting.
|
* @return If the preference is currently set to the default value of the Setting.
|
||||||
*/
|
*/
|
||||||
protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) {
|
protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) {
|
||||||
|
Object defaultValue = setting.defaultValue;
|
||||||
if (pref instanceof SwitchPreference switchPref) {
|
if (pref instanceof SwitchPreference switchPref) {
|
||||||
return switchPref.isChecked() == (Boolean) setting.defaultValue;
|
return switchPref.isChecked() == (Boolean) defaultValue;
|
||||||
}
|
}
|
||||||
|
String defaultValueString = defaultValue.toString();
|
||||||
if (pref instanceof EditTextPreference editPreference) {
|
if (pref instanceof EditTextPreference editPreference) {
|
||||||
return editPreference.getText().equals(setting.defaultValue.toString());
|
return editPreference.getText().equals(defaultValueString);
|
||||||
}
|
}
|
||||||
if (pref instanceof ListPreference listPref) {
|
if (pref instanceof ListPreference listPref) {
|
||||||
return listPref.getValue().equals(setting.defaultValue.toString());
|
return listPref.getValue().equals(defaultValueString);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalStateException("Must override method to handle "
|
throw new IllegalStateException("Must override method to handle "
|
||||||
@@ -158,16 +178,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
/**
|
/**
|
||||||
* Syncs all UI Preferences to any {@link Setting} they represent.
|
* Syncs all UI Preferences to any {@link Setting} they represent.
|
||||||
*/
|
*/
|
||||||
private void updatePreferenceScreen(@NonNull PreferenceScreen screen,
|
private void updatePreferenceScreen(@NonNull PreferenceGroup group,
|
||||||
boolean syncSettingValue,
|
boolean syncSettingValue,
|
||||||
boolean applySettingToPreference) {
|
boolean applySettingToPreference) {
|
||||||
// Alternatively this could iterate thru all Settings and check for any matching Preferences,
|
// Alternatively this could iterate thru all Settings and check for any matching Preferences,
|
||||||
// but there are many more Settings than UI preferences so it's more efficient to only check
|
// but there are many more Settings than UI preferences so it's more efficient to only check
|
||||||
// the Preferences.
|
// the Preferences.
|
||||||
for (int i = 0, prefCount = screen.getPreferenceCount(); i < prefCount; i++) {
|
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||||
Preference pref = screen.getPreference(i);
|
Preference pref = group.getPreference(i);
|
||||||
if (pref instanceof PreferenceScreen) {
|
if (pref instanceof PreferenceGroup subGroup) {
|
||||||
updatePreferenceScreen((PreferenceScreen) pref, syncSettingValue, applySettingToPreference);
|
updatePreferenceScreen(subGroup, syncSettingValue, applySettingToPreference);
|
||||||
} else if (pref.hasKey()) {
|
} else if (pref.hasKey()) {
|
||||||
String key = pref.getKey();
|
String key = pref.getKey();
|
||||||
Setting<?> setting = Setting.getSettingFromPath(key);
|
Setting<?> setting = Setting.getSettingFromPath(key);
|
||||||
@@ -255,7 +275,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showRestartDialog(@NonNull final Context context) {
|
public static void showRestartDialog(Context context) {
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
if (restartDialogTitle == null) {
|
if (restartDialogTitle == null) {
|
||||||
restartDialogTitle = str("revanced_settings_restart_title");
|
restartDialogTitle = str("revanced_settings_restart_title");
|
||||||
@@ -263,6 +283,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
if (restartDialogButtonText == null) {
|
if (restartDialogButtonText == null) {
|
||||||
restartDialogButtonText = str("revanced_settings_restart");
|
restartDialogButtonText = str("revanced_settings_restart");
|
||||||
}
|
}
|
||||||
|
|
||||||
new AlertDialog.Builder(context)
|
new AlertDialog.Builder(context)
|
||||||
.setMessage(restartDialogTitle)
|
.setMessage(restartDialogTitle)
|
||||||
.setPositiveButton(restartDialogButtonText, (dialog, id)
|
.setPositiveButton(restartDialogButtonText, (dialog, id)
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.PreferenceCategory;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty preference category with no title, used to organize and group related preferences together.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
|
public class NoTitlePreferenceCategory extends PreferenceCategory {
|
||||||
|
|
||||||
|
public NoTitlePreferenceCategory(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoTitlePreferenceCategory(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressLint("MissingSuperCall")
|
||||||
|
protected View onCreateView(ViewGroup parent) {
|
||||||
|
// Return an zero-height view to eliminate empty title space.
|
||||||
|
return new View(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getTitle() {
|
||||||
|
// Title can be used for sorting. Return the first sub preference title.
|
||||||
|
if (getPreferenceCount() > 0) {
|
||||||
|
return getPreference(0).getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTitleRes() {
|
||||||
|
if (getPreferenceCount() > 0) {
|
||||||
|
return getPreference(0).getTitleRes();
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getTitleRes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package app.revanced.extension.shared.settings.preference;
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -8,17 +10,23 @@ import android.util.AttributeSet;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import androidx.annotation.Nullable;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
|
||||||
import app.revanced.extension.shared.Logger;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class ResettableEditTextPreference extends EditTextPreference {
|
public class ResettableEditTextPreference extends EditTextPreference {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting to reset.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private Setting<?> setting;
|
||||||
|
|
||||||
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
}
|
}
|
||||||
@@ -32,12 +40,22 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSetting(@Nullable Setting<?> setting) {
|
||||||
|
this.setting = setting;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
super.onPrepareDialogBuilder(builder);
|
super.onPrepareDialogBuilder(builder);
|
||||||
Utils.setEditTextDialogTheme(builder);
|
Utils.setEditTextDialogTheme(builder);
|
||||||
|
|
||||||
Setting<?> setting = Setting.getSettingFromPath(getKey());
|
if (setting == null) {
|
||||||
|
String key = getKey();
|
||||||
|
if (key != null) {
|
||||||
|
setting = Setting.getSettingFromPath(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (setting != null) {
|
if (setting != null) {
|
||||||
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
||||||
}
|
}
|
||||||
@@ -54,8 +72,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||||||
}
|
}
|
||||||
button.setOnClickListener(v -> {
|
button.setOnClickListener(v -> {
|
||||||
try {
|
try {
|
||||||
Setting<?> setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
|
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
|
||||||
String defaultStringValue = setting.defaultValue.toString();
|
|
||||||
EditText editText = getEditText();
|
EditText editText = getEditText();
|
||||||
editText.setText(defaultStringValue);
|
editText.setText(defaultStringValue);
|
||||||
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
||||||
|
|||||||
@@ -66,22 +66,6 @@ public enum ClientType {
|
|||||||
true,
|
true,
|
||||||
"Android Creator"
|
"Android Creator"
|
||||||
),
|
),
|
||||||
ANDROID_VR(
|
|
||||||
ANDROID_VR_NO_AUTH.id,
|
|
||||||
ANDROID_VR_NO_AUTH.clientName,
|
|
||||||
ANDROID_VR_NO_AUTH.packageName,
|
|
||||||
ANDROID_VR_NO_AUTH.deviceMake,
|
|
||||||
ANDROID_VR_NO_AUTH.deviceModel,
|
|
||||||
ANDROID_VR_NO_AUTH.osName,
|
|
||||||
ANDROID_VR_NO_AUTH.osVersion,
|
|
||||||
ANDROID_VR_NO_AUTH.androidSdkVersion,
|
|
||||||
ANDROID_VR_NO_AUTH.buildId,
|
|
||||||
ANDROID_VR_NO_AUTH.cronetVersion,
|
|
||||||
ANDROID_VR_NO_AUTH.clientVersion,
|
|
||||||
ANDROID_VR_NO_AUTH.requiresAuth,
|
|
||||||
true,
|
|
||||||
"Android VR"
|
|
||||||
),
|
|
||||||
IOS_UNPLUGGED(
|
IOS_UNPLUGGED(
|
||||||
33,
|
33,
|
||||||
"IOS_UNPLUGGED",
|
"IOS_UNPLUGGED",
|
||||||
@@ -112,6 +96,22 @@ public enum ClientType {
|
|||||||
forceAVC()
|
forceAVC()
|
||||||
? "iOS TV Force AVC"
|
? "iOS TV Force AVC"
|
||||||
: "iOS TV"
|
: "iOS TV"
|
||||||
|
),
|
||||||
|
ANDROID_VR_AUTH(
|
||||||
|
ANDROID_VR_NO_AUTH.id,
|
||||||
|
ANDROID_VR_NO_AUTH.clientName,
|
||||||
|
ANDROID_VR_NO_AUTH.packageName,
|
||||||
|
ANDROID_VR_NO_AUTH.deviceMake,
|
||||||
|
ANDROID_VR_NO_AUTH.deviceModel,
|
||||||
|
ANDROID_VR_NO_AUTH.osName,
|
||||||
|
ANDROID_VR_NO_AUTH.osVersion,
|
||||||
|
ANDROID_VR_NO_AUTH.androidSdkVersion,
|
||||||
|
ANDROID_VR_NO_AUTH.buildId,
|
||||||
|
ANDROID_VR_NO_AUTH.cronetVersion,
|
||||||
|
ANDROID_VR_NO_AUTH.clientVersion,
|
||||||
|
ANDROID_VR_NO_AUTH.requiresAuth,
|
||||||
|
true,
|
||||||
|
"Android VR Auth"
|
||||||
);
|
);
|
||||||
|
|
||||||
private static boolean forceAVC() {
|
private static boolean forceAVC() {
|
||||||
|
|||||||
@@ -107,6 +107,36 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* Turns off a feature flag that interferes with spoofing.
|
||||||
|
*/
|
||||||
|
public static boolean useMediaFetchHotConfigReplacement(boolean original) {
|
||||||
|
if (original) {
|
||||||
|
Logger.printDebug(() -> "useMediaFetchHotConfigReplacement is set on");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SPOOF_STREAMING_DATA) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* Turns off a feature flag that interferes with video playback.
|
||||||
|
*/
|
||||||
|
public static boolean usePlaybackStartFeatureFlag(boolean original) {
|
||||||
|
if (original) {
|
||||||
|
Logger.printDebug(() -> "usePlaybackStartFeatureFlag is set on");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SPOOF_STREAMING_DATA) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ public class StreamingDataRequest {
|
|||||||
// but empty response body does.
|
// but empty response body does.
|
||||||
if (connection.getContentLength() == 0) {
|
if (connection.getContentLength() == 0) {
|
||||||
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
|
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
|
||||||
Utils.showToastShort("Ignoring empty spoof stream client: " + clientType);
|
Utils.showToastShort("Debug: Ignoring empty spoof stream client " + clientType);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||||
|
|||||||
16
extensions/spotify/build.gradle.kts
Normal file
16
extensions/spotify/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:shared:library"))
|
||||||
|
compileOnly(project(":extensions:spotify:stub"))
|
||||||
|
compileOnly(libs.annotation)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 24
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
1
extensions/spotify/src/main/AndroidManifest.xml
Normal file
1
extensions/spotify/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package app.revanced.extension.spotify.layout.theme;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class CustomThemePatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static long getThemeColor(String colorString) {
|
||||||
|
try {
|
||||||
|
return Utils.getColorFromString(colorString);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Invalid custom color: " + colorString, ex);
|
||||||
|
return Color.BLACK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package app.revanced.extension.spotify.misc.privacy;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class SanitizeSharingLinksPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters that are considered undesirable and should be stripped away.
|
||||||
|
*/
|
||||||
|
private static final List<String> SHARE_PARAMETERS_TO_REMOVE = List.of(
|
||||||
|
"si", // Share tracking parameter.
|
||||||
|
"utm_source" // Share source, such as "copy-link".
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static String sanitizeUrl(String url) {
|
||||||
|
try {
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
Uri.Builder builder = uri.buildUpon().clearQuery();
|
||||||
|
|
||||||
|
for (String paramName : uri.getQueryParameterNames()) {
|
||||||
|
if (!SHARE_PARAMETERS_TO_REMOVE.contains(paramName)) {
|
||||||
|
for (String value : uri.getQueryParameters(paramName)) {
|
||||||
|
builder.appendQueryParameter(paramName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build().toString();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "sanitizeUrl failure", ex);
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
extensions/spotify/stub/build.gradle.kts
Normal file
17
extensions/spotify/stub/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
plugins {
|
||||||
|
id(libs.plugins.android.library.get().pluginId)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
}
|
||||||
1
extensions/spotify/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/spotify/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.spotify.home.evopage.homeapi.proto;
|
||||||
|
|
||||||
|
public final class Section {
|
||||||
|
public static final int VIDEO_BRAND_AD_FIELD_NUMBER = 20;
|
||||||
|
public static final int IMAGE_BRAND_AD_FIELD_NUMBER = 21;
|
||||||
|
public int featureTypeCase_;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.spotify.remoteconfig.internal;
|
||||||
|
|
||||||
|
public final class AccountAttribute {
|
||||||
|
public Object value_;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.spotify.useraccount.v1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for target 8.6.98.900. Class is still present in newer app targets.
|
||||||
|
*/
|
||||||
|
public class AccountAttribute {
|
||||||
|
public Object value_;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package app.revanced.extension.tiktok.feedfilter;
|
|||||||
|
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
import com.ss.android.ugc.aweme.feed.model.FeedItemList;
|
import com.ss.android.ugc.aweme.feed.model.FeedItemList;
|
||||||
|
import com.ss.android.ugc.aweme.follow.presenter.FollowFeedList;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -13,22 +14,41 @@ public final class FeedItemsFilter {
|
|||||||
new StoryFilter(),
|
new StoryFilter(),
|
||||||
new ImageVideoFilter(),
|
new ImageVideoFilter(),
|
||||||
new ViewCountFilter(),
|
new ViewCountFilter(),
|
||||||
new LikeCountFilter()
|
new LikeCountFilter(),
|
||||||
|
new ShopFilter()
|
||||||
);
|
);
|
||||||
|
|
||||||
public static void filter(FeedItemList feedItemList) {
|
public static void filter(FeedItemList feedItemList) {
|
||||||
Iterator<Aweme> feedItemListIterator = feedItemList.items.iterator();
|
filterFeedList(feedItemList.items, item -> item);
|
||||||
while (feedItemListIterator.hasNext()) {
|
}
|
||||||
Aweme item = feedItemListIterator.next();
|
|
||||||
if (item == null) continue;
|
|
||||||
|
|
||||||
for (IFilter filter : FILTERS) {
|
public static void filter(FollowFeedList followFeedList) {
|
||||||
boolean enabled = filter.getEnabled();
|
filterFeedList(followFeedList.mItems, feed -> (feed != null) ? feed.aweme : null);
|
||||||
if (enabled && filter.getFiltered(item)) {
|
}
|
||||||
feedItemListIterator.remove();
|
|
||||||
break;
|
private static <T> void filterFeedList(List<T> list, AwemeExtractor<T> extractor) {
|
||||||
}
|
// Could be simplified with removeIf() but requires Android 7.0+ while TikTok supports 4.0+.
|
||||||
|
Iterator<T> iterator = list.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
T container = iterator.next();
|
||||||
|
Aweme item = extractor.extract(container);
|
||||||
|
if (item != null && shouldFilter(item)) {
|
||||||
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private static boolean shouldFilter(Aweme item) {
|
||||||
|
for (IFilter filter : FILTERS) {
|
||||||
|
if (filter.getEnabled() && filter.getFiltered(item)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface AwemeExtractor<T> {
|
||||||
|
Aweme extract(T source);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.extension.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import app.revanced.extension.tiktok.settings.Settings;
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
|
public class ShopFilter implements IFilter {
|
||||||
|
private static final String SHOP_INFO = "placeholder_product_id";
|
||||||
|
@Override
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return Settings.HIDE_SHOP.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFiltered(Aweme item) {
|
||||||
|
return item.getShareUrl().contains(SHOP_INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import app.revanced.extension.shared.settings.StringSetting;
|
|||||||
public class Settings extends BaseSettings {
|
public class Settings extends BaseSettings {
|
||||||
public static final BooleanSetting REMOVE_ADS = new BooleanSetting("remove_ads", TRUE, true);
|
public static final BooleanSetting REMOVE_ADS = new BooleanSetting("remove_ads", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_LIVE = new BooleanSetting("hide_live", FALSE, true);
|
public static final BooleanSetting HIDE_LIVE = new BooleanSetting("hide_live", FALSE, true);
|
||||||
|
public static final BooleanSetting HIDE_SHOP = new BooleanSetting("hide_shop", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_STORY = new BooleanSetting("hide_story", FALSE, true);
|
public static final BooleanSetting HIDE_STORY = new BooleanSetting("hide_story", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_IMAGE = new BooleanSetting("hide_image", FALSE, true);
|
public static final BooleanSetting HIDE_IMAGE = new BooleanSetting("hide_image", FALSE, true);
|
||||||
public static final StringSetting MIN_MAX_VIEWS = new StringSetting("min_max_views", "0-" + Long.MAX_VALUE, true);
|
public static final StringSetting MIN_MAX_VIEWS = new StringSetting("min_max_views", "0-" + Long.MAX_VALUE, true);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import app.revanced.extension.tiktok.settings.preference.categories.DownloadsPre
|
|||||||
import app.revanced.extension.tiktok.settings.preference.categories.FeedFilterPreferenceCategory;
|
import app.revanced.extension.tiktok.settings.preference.categories.FeedFilterPreferenceCategory;
|
||||||
import app.revanced.extension.tiktok.settings.preference.categories.ExtensionPreferenceCategory;
|
import app.revanced.extension.tiktok.settings.preference.categories.ExtensionPreferenceCategory;
|
||||||
import app.revanced.extension.tiktok.settings.preference.categories.SimSpoofPreferenceCategory;
|
import app.revanced.extension.tiktok.settings.preference.categories.SimSpoofPreferenceCategory;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preference fragment for ReVanced settings
|
* Preference fragment for ReVanced settings
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory
|
|||||||
"Remove feed ads", "Remove ads from feed.",
|
"Remove feed ads", "Remove ads from feed.",
|
||||||
Settings.REMOVE_ADS
|
Settings.REMOVE_ADS
|
||||||
));
|
));
|
||||||
|
addPreference(new TogglePreference(
|
||||||
|
context,
|
||||||
|
"Hide TikTok Shop", "Hide TikTok shop from feed.",
|
||||||
|
Settings.HIDE_SHOP
|
||||||
|
));
|
||||||
addPreference(new TogglePreference(
|
addPreference(new TogglePreference(
|
||||||
context,
|
context,
|
||||||
"Hide livestreams", "Hide livestreams from feed.",
|
"Hide livestreams", "Hide livestreams from feed.",
|
||||||
|
|||||||
@@ -1,37 +1,60 @@
|
|||||||
package app.revanced.extension.tiktok.spoof.sim;
|
package app.revanced.extension.tiktok.spoof.sim;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.tiktok.settings.Settings;
|
import app.revanced.extension.tiktok.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofSimPatch {
|
public class SpoofSimPatch {
|
||||||
|
|
||||||
private static final boolean ENABLED = Settings.SIM_SPOOF.get();
|
/**
|
||||||
|
* During app startup native code can be called with no obvious way to set the context.
|
||||||
|
* Cannot check if sim spoofing is enabled or the app will crash since no context is set.
|
||||||
|
*/
|
||||||
|
private static boolean isContextNotSet(String fieldSpoofed) {
|
||||||
|
if (Utils.getContext() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.initializationException(SpoofSimPatch.class,
|
||||||
|
"Context is not yet set, cannot spoof: " + fieldSpoofed, null);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getCountryIso(String value) {
|
public static String getCountryIso(String value) {
|
||||||
if (ENABLED) {
|
if (isContextNotSet("countryIso")) return value;
|
||||||
|
|
||||||
|
if (Settings.SIM_SPOOF.get()) {
|
||||||
String iso = Settings.SIM_SPOOF_ISO.get();
|
String iso = Settings.SIM_SPOOF_ISO.get();
|
||||||
Logger.printDebug(() -> "Spoofing sim ISO from: " + value + " to: " + iso);
|
Logger.printDebug(() -> "Spoofing countryIso from: " + value + " to: " + iso);
|
||||||
return iso;
|
return iso;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getOperator(String value) {
|
public static String getOperator(String value) {
|
||||||
if (ENABLED) {
|
if (isContextNotSet("MCC-MNC")) return value;
|
||||||
|
|
||||||
|
if (Settings.SIM_SPOOF.get()) {
|
||||||
String mcc_mnc = Settings.SIMSPOOF_MCCMNC.get();
|
String mcc_mnc = Settings.SIMSPOOF_MCCMNC.get();
|
||||||
Logger.printDebug(() -> "Spoofing sim MCC-MNC from: " + value + " to: " + mcc_mnc);
|
Logger.printDebug(() -> "Spoofing sim MCC-MNC from: " + value + " to: " + mcc_mnc);
|
||||||
return mcc_mnc;
|
return mcc_mnc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getOperatorName(String value) {
|
public static String getOperatorName(String value) {
|
||||||
if (ENABLED) {
|
if (isContextNotSet("operatorName")) return value;
|
||||||
|
|
||||||
|
if (Settings.SIM_SPOOF.get()) {
|
||||||
String operator = Settings.SIMSPOOF_OP_NAME.get();
|
String operator = Settings.SIMSPOOF_OP_NAME.get();
|
||||||
Logger.printDebug(() -> "Spoofing sim operator from: " + value + " to: " + operator);
|
Logger.printDebug(() -> "Spoofing sim operatorName from: " + value + " to: " + operator);
|
||||||
return operator;
|
return operator;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,4 +33,8 @@ public class Aweme {
|
|||||||
public AwemeStatistics getStatistics() {
|
public AwemeStatistics getStatistics() {
|
||||||
throw new UnsupportedOperationException("Stub");
|
throw new UnsupportedOperationException("Stub");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getShareUrl() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.ss.android.ugc.aweme.follow.presenter;
|
||||||
|
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
|
//Dummy class
|
||||||
|
public class FollowFeed {
|
||||||
|
public Aweme aweme;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.ss.android.ugc.aweme.follow.presenter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
//Dummy class
|
||||||
|
public class FollowFeedList {
|
||||||
|
public List<FollowFeed> mItems;
|
||||||
|
}
|
||||||
@@ -163,7 +163,7 @@ internal object TwiFucker {
|
|||||||
|
|
||||||
private fun JSONObject.entryIsWhoToFollow(): Boolean =
|
private fun JSONObject.entryIsWhoToFollow(): Boolean =
|
||||||
optString("entryId").let {
|
optString("entryId").let {
|
||||||
it.startsWith("whoToFollow-") || it.startsWith("who-to-follow-") || it.startsWith("connect-module-")
|
it.startsWith("whoToFollow-") || it.startsWith("who-to-follow-") || it.startsWith("connect-module-") || it.startsWith("who-to-subscribe-")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JSONObject.itemContainsPromotedUser(): Boolean =
|
private fun JSONObject.itemContainsPromotedUser(): Boolean =
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = 33 // TODO: Update Swipe controls code to allow updating this to the latest sdk.
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package app.revanced.extension.youtube;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.Window;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@@ -45,13 +47,24 @@ public class ThemeHelper {
|
|||||||
return "@color/yt_black3";
|
return "@color/yt_black3";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getThemeColor(String resourceName, int defaultColor) {
|
||||||
|
try {
|
||||||
|
return Utils.getColorFromString(resourceName);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// User entered an invalid custom theme color.
|
||||||
|
// Normally this should never be reached, and no localized strings are needed.
|
||||||
|
Utils.showToastLong("Invalid custom theme color: " + resourceName);
|
||||||
|
return defaultColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The dark theme color as specified by the Theme patch (if included),
|
* @return The dark theme color as specified by the Theme patch (if included),
|
||||||
* or the dark mode background color unpatched YT uses.
|
* or the dark mode background color unpatched YT uses.
|
||||||
*/
|
*/
|
||||||
public static int getDarkThemeColor() {
|
public static int getDarkThemeColor() {
|
||||||
if (darkThemeColor == null) {
|
if (darkThemeColor == null) {
|
||||||
darkThemeColor = getColorInt(darkThemeResourceName());
|
darkThemeColor = getThemeColor(darkThemeResourceName(), Color.BLACK);
|
||||||
}
|
}
|
||||||
return darkThemeColor;
|
return darkThemeColor;
|
||||||
}
|
}
|
||||||
@@ -71,18 +84,11 @@ public class ThemeHelper {
|
|||||||
*/
|
*/
|
||||||
public static int getLightThemeColor() {
|
public static int getLightThemeColor() {
|
||||||
if (lightThemeColor == null) {
|
if (lightThemeColor == null) {
|
||||||
lightThemeColor = getColorInt(lightThemeResourceName());
|
lightThemeColor = getThemeColor(lightThemeResourceName(), Color.WHITE);
|
||||||
}
|
}
|
||||||
return lightThemeColor;
|
return lightThemeColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getColorInt(String colorString) {
|
|
||||||
if (colorString.startsWith("#")) {
|
|
||||||
return Color.parseColor(colorString);
|
|
||||||
}
|
|
||||||
return Utils.getResourceColor(colorString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getBackgroundColor() {
|
public static int getBackgroundColor() {
|
||||||
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
|
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
|
||||||
}
|
}
|
||||||
@@ -96,6 +102,23 @@ public class ThemeHelper {
|
|||||||
? "yt_black3"
|
? "yt_black3"
|
||||||
: "yt_white1";
|
: "yt_white1";
|
||||||
|
|
||||||
return getColorInt(colorName);
|
return Utils.getColorFromString(colorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the system navigation bar color for the activity.
|
||||||
|
* Applies the background color obtained from {@link #getBackgroundColor()} to the navigation bar.
|
||||||
|
* For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility.
|
||||||
|
*/
|
||||||
|
public static void setNavigationBarColor(@Nullable Window window) {
|
||||||
|
if (window == null) {
|
||||||
|
Logger.printDebug(() -> "Cannot set navigation bar color, window is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setNavigationBarColor(getBackgroundColor());
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
window.setNavigationBarContrastEnforced(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.StringRef.sf;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class AccountCredentialsInvalidTextPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static String getOfflineNetworkErrorString(String original) {
|
||||||
|
try {
|
||||||
|
if (Utils.isNetworkConnected()) {
|
||||||
|
Logger.printDebug(() -> "Network appears to be online, but app is showing offline error");
|
||||||
|
return '\n' + sf("microg_offline_account_login_error").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Network is offline");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "getOfflineNetworkErrorString failure", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package app.revanced.extension.youtube.patches;
|
|||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class BackgroundPlaybackPatch {
|
public class BackgroundPlaybackPatch {
|
||||||
@@ -23,16 +24,13 @@ public class BackgroundPlaybackPatch {
|
|||||||
// 7. Close the Short
|
// 7. Close the Short
|
||||||
// 8. Resume playing the regular video
|
// 8. Resume playing the regular video
|
||||||
// 9. Minimize the app (PIP should appear)
|
// 9. Minimize the app (PIP should appear)
|
||||||
if (!VideoInformation.lastVideoIdIsShort()) {
|
if (ShortsPlayerState.isOpen()) {
|
||||||
return true; // Definitely is not a Short.
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add better hook.
|
// Check if the video player is opened and it's not playing in the feed.
|
||||||
// Might be a Shorts, or might be a prior regular video on screen again after a Shorts was closed.
|
PlayerType current = PlayerType.getCurrent();
|
||||||
// This incorrectly prevents PIP if player is in WATCH_WHILE_MINIMIZED after closing a Shorts,
|
return !current.isNoneOrHidden() && current != PlayerType.INLINE_MINIMAL;
|
||||||
// But there's no way around this unless an additional hook is added to definitively detect
|
|
||||||
// the Shorts player is on screen. This use case is unusual anyways so it's not a huge concern.
|
|
||||||
return !PlayerType.getCurrent().isNoneHiddenOrMinimized();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||||
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ChangeFormFactorPatch {
|
public class ChangeFormFactorPatch {
|
||||||
@@ -41,14 +49,57 @@ public class ChangeFormFactorPatch {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
|
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
|
||||||
|
private static final boolean USING_AUTOMOTIVE_TYPE = Objects.requireNonNull(
|
||||||
|
FormFactor.AUTOMOTIVE.formFactorType).equals(FORM_FACTOR_TYPE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static int getFormFactor(int original) {
|
public static int getFormFactor(int original) {
|
||||||
return FORM_FACTOR_TYPE == null
|
if (FORM_FACTOR_TYPE == null) return original;
|
||||||
? original
|
|
||||||
: FORM_FACTOR_TYPE;
|
if (USING_AUTOMOTIVE_TYPE) {
|
||||||
|
// Do not change if the player is opening or is opened,
|
||||||
|
// otherwise the video description cannot be opened.
|
||||||
|
PlayerType current = PlayerType.getCurrent();
|
||||||
|
if (current.isMaximizedOrFullscreen() || current == PlayerType.WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED) {
|
||||||
|
Logger.printDebug(() -> "Using original form factor for player");
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NavigationBar.isSearchBarActive()) {
|
||||||
|
// Automotive type shows error 400 when opening a channel page and using some explore tab.
|
||||||
|
// This is a bug in unpatched YouTube that occurs on actual Android Automotive devices.
|
||||||
|
// Work around the issue by using the original form factor if not in search and the
|
||||||
|
// navigation back button is present.
|
||||||
|
if (NavigationBar.isBackButtonVisible()) {
|
||||||
|
Logger.printDebug(() -> "Using original form factor, as back button is visible without search present");
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not change library tab otherwise watch history is hidden.
|
||||||
|
// Do this check last since the current navigation button is required.
|
||||||
|
if (NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FORM_FACTOR_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
||||||
|
// On first startup of the app the navigation buttons are fetched and updated.
|
||||||
|
// If the user immediately opens the 'You' or opens a video, then the call to
|
||||||
|
// update the navigtation buttons will use the non automotive form factor
|
||||||
|
// and the explore tab is missing.
|
||||||
|
// Fixing this is not so simple because of the concurrent calls for the player and You tab.
|
||||||
|
// For now, always hide the explore tab.
|
||||||
|
if (USING_AUTOMOTIVE_TYPE && button == NavigationButton.EXPLORE) {
|
||||||
|
tabView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -81,6 +82,13 @@ public final class ChangeStartPagePatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ChangeStartPageTypeAvailability implements Setting.Availability {
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return Settings.CHANGE_START_PAGE.get() != StartPage.DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Intent action when YouTube is cold started from the launcher.
|
* Intent action when YouTube is cold started from the launcher.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -93,6 +101,8 @@ public final class ChangeStartPagePatch {
|
|||||||
|
|
||||||
private static final StartPage START_PAGE = Settings.CHANGE_START_PAGE.get();
|
private static final StartPage START_PAGE = Settings.CHANGE_START_PAGE.get();
|
||||||
|
|
||||||
|
private static final boolean CHANGE_START_PAGE_ALWAYS = Settings.CHANGE_START_PAGE_ALWAYS.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There is an issue where the back button on the toolbar doesn't work properly.
|
* There is an issue where the back button on the toolbar doesn't work properly.
|
||||||
* As a workaround for this issue, instead of overriding the browserId multiple times, just override it once.
|
* As a workaround for this issue, instead of overriding the browserId multiple times, just override it once.
|
||||||
@@ -104,13 +114,13 @@ public final class ChangeStartPagePatch {
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appLaunched) {
|
if (!CHANGE_START_PAGE_ALWAYS && appLaunched) {
|
||||||
Logger.printDebug(() -> "Ignore override browseId as the app already launched");
|
Logger.printDebug(() -> "Ignore override browseId as the app already launched");
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
appLaunched = true;
|
appLaunched = true;
|
||||||
|
|
||||||
Logger.printDebug(() -> "Changing browseId to " + START_PAGE.id);
|
Logger.printDebug(() -> "Changing browseId to: " + START_PAGE.id);
|
||||||
return START_PAGE.id;
|
return START_PAGE.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,14 +135,14 @@ public final class ChangeStartPagePatch {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appLaunched) {
|
if (!CHANGE_START_PAGE_ALWAYS && appLaunched) {
|
||||||
Logger.printDebug(() -> "Ignore override intent action as the app already launched");
|
Logger.printDebug(() -> "Ignore override intent action as the app already launched");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
appLaunched = true;
|
appLaunched = true;
|
||||||
|
|
||||||
String intentAction = START_PAGE.id;
|
String intentAction = START_PAGE.id;
|
||||||
Logger.printDebug(() -> "Changing intent action to " + intentAction);
|
Logger.printDebug(() -> "Changing intent action to: " + intentAction);
|
||||||
intent.setAction(intentAction);
|
intent.setAction(intentAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public class CustomPlayerOverlayOpacityPatch {
|
|||||||
|
|
||||||
if (opacity < 0 || opacity > 100) {
|
if (opacity < 0 || opacity > 100) {
|
||||||
Utils.showToastLong(str("revanced_player_overlay_opacity_invalid_toast"));
|
Utils.showToastLong(str("revanced_player_overlay_opacity_invalid_toast"));
|
||||||
Settings.PLAYER_OVERLAY_OPACITY.resetToDefault();
|
opacity = Settings.PLAYER_OVERLAY_OPACITY.resetToDefault();
|
||||||
opacity = Settings.PLAYER_OVERLAY_OPACITY.defaultValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PLAYER_OVERLAY_OPACITY_LEVEL = (opacity * 255) / 100;
|
PLAYER_OVERLAY_OPACITY_LEVEL = (opacity * 255) / 100;
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class DisableAutoCaptionsPatch {
|
public class DisableAutoCaptionsPatch {
|
||||||
|
|
||||||
/**
|
private static volatile boolean captionsButtonStatus;
|
||||||
* Used by injected code. Do not delete.
|
|
||||||
*/
|
|
||||||
public static boolean captionsButtonDisabled;
|
|
||||||
|
|
||||||
public static boolean autoCaptionsEnabled() {
|
/**
|
||||||
return Settings.AUTO_CAPTIONS.get()
|
* Injection point.
|
||||||
// Do not use auto captions for Shorts.
|
*/
|
||||||
&& !PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized();
|
public static boolean disableAutoCaptions() {
|
||||||
|
return Settings.DISABLE_AUTO_CAPTIONS.get() && !captionsButtonStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setCaptionsButtonStatus(boolean status) {
|
||||||
|
captionsButtonStatus = status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package app.revanced.extension.youtube.patches;
|
|||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
/** @noinspection unused*/
|
@SuppressWarnings("unused")
|
||||||
public class DisableResumingStartupShortsPlayerPatch {
|
public class DisableResumingStartupShortsPlayerPatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,4 +11,11 @@ public class DisableResumingStartupShortsPlayerPatch {
|
|||||||
public static boolean disableResumingStartupShortsPlayer() {
|
public static boolean disableResumingStartupShortsPlayer() {
|
||||||
return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get();
|
return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean disableResumingStartupShortsPlayer(boolean original) {
|
||||||
|
return original && !Settings.DISABLE_RESUMING_SHORTS_PLAYER.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
|
||||||
|
|
||||||
/** @noinspection unused*/
|
|
||||||
public final class DisableSuggestedVideoEndScreenPatch {
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
private static ImageView lastView;
|
|
||||||
|
|
||||||
public static void closeEndScreen(final ImageView imageView) {
|
|
||||||
if (!Settings.DISABLE_SUGGESTED_VIDEO_END_SCREEN.get()) return;
|
|
||||||
|
|
||||||
// Prevent adding the listener multiple times.
|
|
||||||
if (lastView == imageView) return;
|
|
||||||
lastView = imageView;
|
|
||||||
|
|
||||||
imageView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
|
||||||
if (imageView.isShown()) imageView.callOnClick();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,7 @@ public final class EnableDebuggingPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) {
|
public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
|
||||||
if (LOG_FEATURE_FLAGS && value) {
|
if (LOG_FEATURE_FLAGS && value) {
|
||||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||||
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class HideEndScreenSuggestedVideoPatch {
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean hideEndScreenSuggestedVideo() {
|
||||||
|
return Settings.HIDE_END_SCREEN_SUGGESTED_VIDEO.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,10 +43,13 @@ public final class MiniplayerPatch {
|
|||||||
MODERN_2(null, 2),
|
MODERN_2(null, 2),
|
||||||
MODERN_3(null, 3),
|
MODERN_3(null, 3),
|
||||||
/**
|
/**
|
||||||
* Half broken miniplayer, that might be work in progress or left over abandoned code.
|
* Works and is functional with 20.03+
|
||||||
* Can force this type by editing the import/export settings.
|
|
||||||
*/
|
*/
|
||||||
MODERN_4(null, 4);
|
MODERN_4(null, 4),
|
||||||
|
/**
|
||||||
|
* Half broken miniplayer, and in 20.02 and earlier is declared as type 4.
|
||||||
|
*/
|
||||||
|
MODERN_5(null, 5);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legacy tablet hook value.
|
* Legacy tablet hook value.
|
||||||
@@ -126,12 +129,13 @@ public final class MiniplayerPatch {
|
|||||||
private static final boolean DRAG_AND_DROP_ENABLED =
|
private static final boolean DRAG_AND_DROP_ENABLED =
|
||||||
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
|
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
|
||||||
|
|
||||||
private static final boolean HIDE_EXPAND_CLOSE_ENABLED =
|
private static final boolean HIDE_OVERLAY_BUTTONS_ENABLED =
|
||||||
Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.get()
|
Settings.MINIPLAYER_HIDE_OVERLAY_BUTTONS.get()
|
||||||
&& Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.isAvailable();
|
&& Settings.MINIPLAYER_HIDE_OVERLAY_BUTTONS.isAvailable();
|
||||||
|
|
||||||
private static final boolean HIDE_SUBTEXT_ENABLED =
|
private static final boolean HIDE_SUBTEXT_ENABLED =
|
||||||
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) && Settings.MINIPLAYER_HIDE_SUBTEXT.get();
|
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3 || CURRENT_TYPE == MODERN_4)
|
||||||
|
&& Settings.MINIPLAYER_HIDE_SUBTEXT.get();
|
||||||
|
|
||||||
// 19.25 is last version that has forward/back buttons for phones,
|
// 19.25 is last version that has forward/back buttons for phones,
|
||||||
// but buttons still show for tablets/foldable devices and they don't work well so always hide.
|
// but buttons still show for tablets/foldable devices and they don't work well so always hide.
|
||||||
@@ -139,7 +143,7 @@ public final class MiniplayerPatch {
|
|||||||
&& (VersionCheckPatch.IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get());
|
&& (VersionCheckPatch.IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get());
|
||||||
|
|
||||||
private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED =
|
private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED =
|
||||||
Settings.MINIPLAYER_ROUNDED_CORNERS.get();
|
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_ROUNDED_CORNERS.get();
|
||||||
|
|
||||||
private static final boolean MINIPLAYER_HORIZONTAL_DRAG_ENABLED =
|
private static final boolean MINIPLAYER_HORIZONTAL_DRAG_ENABLED =
|
||||||
DRAG_AND_DROP_ENABLED && Settings.MINIPLAYER_HORIZONTAL_DRAG.get();
|
DRAG_AND_DROP_ENABLED && Settings.MINIPLAYER_HORIZONTAL_DRAG.get();
|
||||||
@@ -158,8 +162,7 @@ public final class MiniplayerPatch {
|
|||||||
|
|
||||||
if (opacity < 0 || opacity > 100) {
|
if (opacity < 0 || opacity > 100) {
|
||||||
Utils.showToastLong(str("revanced_miniplayer_opacity_invalid_toast"));
|
Utils.showToastLong(str("revanced_miniplayer_opacity_invalid_toast"));
|
||||||
Settings.MINIPLAYER_OPACITY.resetToDefault();
|
opacity = Settings.MINIPLAYER_OPACITY.resetToDefault();
|
||||||
opacity = Settings.MINIPLAYER_OPACITY.defaultValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OPACITY_LEVEL = (opacity * 255) / 100;
|
OPACITY_LEVEL = (opacity * 255) / 100;
|
||||||
@@ -172,11 +175,12 @@ public final class MiniplayerPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class MiniplayerHideExpandCloseAvailability implements Setting.Availability {
|
public static final class MiniplayerHideOverlayButtonsAvailability implements Setting.Availability {
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
MiniplayerType type = Settings.MINIPLAYER_TYPE.get();
|
MiniplayerType type = Settings.MINIPLAYER_TYPE.get();
|
||||||
return (!IS_19_20_OR_GREATER && (type == MODERN_1 || type == MODERN_3))
|
return type == MODERN_4
|
||||||
|
|| (!IS_19_20_OR_GREATER && (type == MODERN_1 || type == MODERN_3))
|
||||||
|| (!IS_19_26_OR_GREATER && type == MODERN_1
|
|| (!IS_19_26_OR_GREATER && type == MODERN_1
|
||||||
&& !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get())
|
&& !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get())
|
||||||
|| (IS_19_29_OR_GREATER && type == MODERN_3);
|
|| (IS_19_29_OR_GREATER && type == MODERN_3);
|
||||||
@@ -227,9 +231,13 @@ public final class MiniplayerPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void adjustMiniplayerOpacity(ImageView view) {
|
public static void adjustMiniplayerOpacity(View view) {
|
||||||
if (CURRENT_TYPE == MODERN_1) {
|
if (CURRENT_TYPE == MODERN_1) {
|
||||||
view.setImageAlpha(OPACITY_LEVEL);
|
if (view instanceof ImageView imageView) {
|
||||||
|
imageView.setImageAlpha(OPACITY_LEVEL);
|
||||||
|
} else {
|
||||||
|
Logger.printException(() -> "Unknown miniplayer overlay view: " + view);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +255,7 @@ public final class MiniplayerPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean enableMiniplayerDoubleTapAction(boolean original) {
|
public static boolean getMiniplayerDoubleTapAction(boolean original) {
|
||||||
if (CURRENT_TYPE == DEFAULT) {
|
if (CURRENT_TYPE == DEFAULT) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
@@ -258,7 +266,7 @@ public final class MiniplayerPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean enableMiniplayerDragAndDrop(boolean original) {
|
public static boolean getMiniplayerDragAndDrop(boolean original) {
|
||||||
if (CURRENT_TYPE == DEFAULT) {
|
if (CURRENT_TYPE == DEFAULT) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
@@ -266,13 +274,36 @@ public final class MiniplayerPatch {
|
|||||||
return DRAG_AND_DROP_ENABLED;
|
return DRAG_AND_DROP_ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean getRoundedCorners(boolean original) {
|
||||||
|
if (CURRENT_TYPE == DEFAULT) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MINIPLAYER_ROUNDED_CORNERS_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean setRoundedCorners(boolean original) {
|
public static boolean getHorizontalDrag(boolean original) {
|
||||||
if (CURRENT_TYPE.isModern()) {
|
if (CURRENT_TYPE == DEFAULT) {
|
||||||
return MINIPLAYER_ROUNDED_CORNERS_ENABLED;
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MINIPLAYER_HORIZONTAL_DRAG_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean getMaximizeAnimation(boolean original) {
|
||||||
|
// This must be forced on if horizontal drag is enabled,
|
||||||
|
// otherwise the UI has visual glitches when maximizing the miniplayer.
|
||||||
|
if (MINIPLAYER_HORIZONTAL_DRAG_ENABLED) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return original;
|
return original;
|
||||||
@@ -281,7 +312,7 @@ public final class MiniplayerPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static int setMiniplayerDefaultSize(int original) {
|
public static int getMiniplayerDefaultSize(int original) {
|
||||||
if (CURRENT_TYPE.isModern()) {
|
if (CURRENT_TYPE.isModern()) {
|
||||||
return MINIPLAYER_SIZE;
|
return MINIPLAYER_SIZE;
|
||||||
}
|
}
|
||||||
@@ -289,29 +320,26 @@ public final class MiniplayerPatch {
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void hideMiniplayerExpandClose(View view) {
|
||||||
|
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_OVERLAY_BUTTONS_ENABLED, view);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean setHorizontalDrag(boolean original) {
|
public static void hideMiniplayerActionButton(View view) {
|
||||||
if (CURRENT_TYPE.isModern()) {
|
if (CURRENT_TYPE == MODERN_4) {
|
||||||
return MINIPLAYER_HORIZONTAL_DRAG_ENABLED;
|
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_OVERLAY_BUTTONS_ENABLED, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
return original;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void hideMiniplayerExpandClose(ImageView view) {
|
public static void hideMiniplayerRewindForward(View view) {
|
||||||
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_EXPAND_CLOSE_ENABLED, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void hideMiniplayerRewindForward(ImageView view) {
|
|
||||||
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_REWIND_FORWARD_ENABLED, view);
|
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_REWIND_FORWARD_ENABLED, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public final class NavigationButtonsPatch {
|
|||||||
{
|
{
|
||||||
put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get());
|
put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get());
|
||||||
put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get());
|
put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get());
|
||||||
|
put(NavigationButton.NOTIFICATIONS, Settings.HIDE_NOTIFICATIONS_BUTTON.get());
|
||||||
put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get());
|
put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get());
|
||||||
put(NavigationButton.SUBSCRIPTIONS, Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
|
put(NavigationButton.SUBSCRIPTIONS, Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||||
import app.revanced.extension.youtube.shared.VideoState;
|
import app.revanced.extension.youtube.shared.VideoState;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -24,4 +27,26 @@ public class PlayerTypeHookPatch {
|
|||||||
|
|
||||||
VideoState.setFromString(youTubeVideoState.name());
|
VideoState.setFromString(youTubeVideoState.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* Add a listener to the shorts player overlay View.
|
||||||
|
* Triggered when a shorts player is attached or detached to Windows.
|
||||||
|
*
|
||||||
|
* @param view shorts player overlay (R.id.reel_watch_player).
|
||||||
|
*/
|
||||||
|
public static void onShortsCreate(View view) {
|
||||||
|
view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onViewAttachedToWindow(@Nullable View v) {
|
||||||
|
ShortsPlayerState.setOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewDetachedFromWindow(@Nullable View v) {
|
||||||
|
ShortsPlayerState.setOpen(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,21 @@ package app.revanced.extension.youtube.patches;
|
|||||||
|
|
||||||
import static app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike.Vote;
|
import static app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike.Vote;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
|
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
|
||||||
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
|
|
||||||
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
@@ -47,9 +42,6 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ReturnYouTubeDislikePatch {
|
public class ReturnYouTubeDislikePatch {
|
||||||
|
|
||||||
public static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
|
|
||||||
SpoofAppVersionPatch.isSpoofingToLessThan("18.34.00");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RYD data for the current video on screen.
|
* RYD data for the current video on screen.
|
||||||
*/
|
*/
|
||||||
@@ -64,12 +56,12 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
|
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Because the litho Shorts spans are created after {@link ReturnYouTubeDislikeFilterPatch}
|
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilterPatch}
|
||||||
* detects the video ids, after the user votes the litho will update
|
* detects the video ids, but the current Short can arbitrarily reload the same span,
|
||||||
* but {@link #lastLithoShortsVideoData} is not the correct data to use.
|
* then use the {@link #lastLithoShortsVideoData} if this value is greater than zero.
|
||||||
* If this is true, then instead use {@link #currentVideoData}.
|
|
||||||
*/
|
*/
|
||||||
private static volatile boolean lithoShortsShouldUseCurrentData;
|
@GuardedBy("ReturnYouTubeDislikePatch.class")
|
||||||
|
private static int useLithoShortsVideoDataCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last video id prefetched. Field is to prevent prefetching the same video id multiple times in a row.
|
* Last video id prefetched. Field is to prevent prefetching the same video id multiple times in a row.
|
||||||
@@ -87,12 +79,28 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
private static void clearData() {
|
private static void clearData() {
|
||||||
currentVideoData = null;
|
currentVideoData = null;
|
||||||
lastLithoShortsVideoData = null;
|
lastLithoShortsVideoData = null;
|
||||||
lithoShortsShouldUseCurrentData = false;
|
synchronized (ReturnYouTubeDislike.class) {
|
||||||
|
useLithoShortsVideoDataCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Rolling number text should not be cleared,
|
// Rolling number text should not be cleared,
|
||||||
// as it's used if incognito Short is opened/closed
|
// as it's used if incognito Short is opened/closed
|
||||||
// while a regular video is on screen.
|
// while a regular video is on screen.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If {@link #useLithoShortsVideoDataCount} was greater than zero.
|
||||||
|
*/
|
||||||
|
private static boolean decrementUseLithoDataIfNeeded() {
|
||||||
|
synchronized (ReturnYouTubeDislikePatch.class) {
|
||||||
|
if (useLithoShortsVideoDataCount > 0) {
|
||||||
|
useLithoShortsVideoDataCount--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Litho player for both regular videos and Shorts.
|
// Litho player for both regular videos and Shorts.
|
||||||
@@ -156,10 +164,13 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
return getShortsSpan(original, true);
|
return getShortsSpan(original, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conversionContextString.contains("|shorts_like_button.eml")
|
if (conversionContextString.contains("|shorts_like_button.eml")) {
|
||||||
&& !Utils.containsNumber(original)) {
|
if (!Utils.containsNumber(original)) {
|
||||||
Logger.printDebug(() -> "Replacing hidden likes count");
|
Logger.printDebug(() -> "Replacing hidden likes count");
|
||||||
return getShortsSpan(original, false);
|
return getShortsSpan(original, false);
|
||||||
|
} else {
|
||||||
|
decrementUseLithoDataIfNeeded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "onLithoTextLoaded failure", ex);
|
Logger.printException(() -> "onLithoTextLoaded failure", ex);
|
||||||
@@ -174,7 +185,14 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReturnYouTubeDislike videoData = lastLithoShortsVideoData;
|
final ReturnYouTubeDislike videoData;
|
||||||
|
if (decrementUseLithoDataIfNeeded()) {
|
||||||
|
// New Short is loading off screen.
|
||||||
|
videoData = lastLithoShortsVideoData;
|
||||||
|
} else {
|
||||||
|
videoData = currentVideoData;
|
||||||
|
}
|
||||||
|
|
||||||
if (videoData == null) {
|
if (videoData == null) {
|
||||||
// The Shorts litho video id filter did not detect the video id.
|
// The Shorts litho video id filter did not detect the video id.
|
||||||
// This is normal in incognito mode, but otherwise is abnormal.
|
// This is normal in incognito mode, but otherwise is abnormal.
|
||||||
@@ -182,19 +200,6 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the correct dislikes data after voting.
|
|
||||||
if (lithoShortsShouldUseCurrentData) {
|
|
||||||
if (isDislikesSpan) {
|
|
||||||
lithoShortsShouldUseCurrentData = false;
|
|
||||||
}
|
|
||||||
videoData = currentVideoData;
|
|
||||||
if (videoData == null) {
|
|
||||||
Logger.printException(() -> "currentVideoData is null"); // Should never happen
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
Logger.printDebug(() -> "Using current video data for litho span");
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDislikesSpan
|
return isDislikesSpan
|
||||||
? videoData.getDislikeSpanForShort((Spanned) original)
|
? videoData.getDislikeSpanForShort((Spanned) original)
|
||||||
: videoData.getLikeSpanForShort((Spanned) original);
|
: videoData.getLikeSpanForShort((Spanned) original);
|
||||||
@@ -347,137 +352,6 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Non litho Shorts player.
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replacement text to use for "Dislikes" while RYD is fetching.
|
|
||||||
*/
|
|
||||||
private static final Spannable SHORTS_LOADING_SPAN = new SpannableString("-");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dislikes TextViews used by Shorts.
|
|
||||||
*
|
|
||||||
* Multiple TextViews are loaded at once (for the prior and next videos to swipe to).
|
|
||||||
* Keep track of all of them, and later pick out the correct one based on their on screen position.
|
|
||||||
*/
|
|
||||||
private static final List<WeakReference<TextView>> shortsTextViewRefs = new ArrayList<>();
|
|
||||||
|
|
||||||
private static void clearRemovedShortsTextViews() {
|
|
||||||
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point. Called when a Shorts dislike is updated. Always on main thread.
|
|
||||||
* Handles update asynchronously, otherwise Shorts video will be frozen while the UI thread is blocked.
|
|
||||||
*
|
|
||||||
* @return if RYD is enabled and the TextView was updated.
|
|
||||||
*/
|
|
||||||
public static boolean setShortsDislikes(@NonNull View likeDislikeView) {
|
|
||||||
try {
|
|
||||||
if (!Settings.RYD_ENABLED.get()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!Settings.RYD_SHORTS.get() || Settings.HIDE_SHORTS_DISLIKE_BUTTON.get()) {
|
|
||||||
// Must clear the data here, in case a new video was loaded while PlayerType
|
|
||||||
// suggested the video was not a short (can happen when spoofing to an old app version).
|
|
||||||
clearData();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Logger.printDebug(() -> "setShortsDislikes");
|
|
||||||
|
|
||||||
TextView textView = (TextView) likeDislikeView;
|
|
||||||
textView.setText(SHORTS_LOADING_SPAN); // Change 'Dislike' text to the loading text.
|
|
||||||
shortsTextViewRefs.add(new WeakReference<>(textView));
|
|
||||||
|
|
||||||
if (likeDislikeView.isSelected() && isShortTextViewOnScreen(textView)) {
|
|
||||||
Logger.printDebug(() -> "Shorts dislike is already selected");
|
|
||||||
ReturnYouTubeDislike videoData = currentVideoData;
|
|
||||||
if (videoData != null) videoData.setUserVote(Vote.DISLIKE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For the first short played, the Shorts dislike hook is called after the video id hook.
|
|
||||||
// But for most other times this hook is called before the video id (which is not ideal).
|
|
||||||
// Must update the TextViews here, and also after the videoId changes.
|
|
||||||
updateOnScreenShortsTextViews(false);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "setShortsDislikes failure", ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param forceUpdate if false, then only update the 'loading text views.
|
|
||||||
* If true, update all on screen text views.
|
|
||||||
*/
|
|
||||||
private static void updateOnScreenShortsTextViews(boolean forceUpdate) {
|
|
||||||
try {
|
|
||||||
clearRemovedShortsTextViews();
|
|
||||||
if (shortsTextViewRefs.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ReturnYouTubeDislike videoData = currentVideoData;
|
|
||||||
if (videoData == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.printDebug(() -> "updateShortsTextViews");
|
|
||||||
|
|
||||||
Runnable update = () -> {
|
|
||||||
Spanned shortsDislikesSpan = videoData.getDislikeSpanForShort(SHORTS_LOADING_SPAN);
|
|
||||||
Utils.runOnMainThreadNowOrLater(() -> {
|
|
||||||
String videoId = videoData.getVideoId();
|
|
||||||
if (!videoId.equals(VideoInformation.getVideoId())) {
|
|
||||||
// User swiped to new video before fetch completed
|
|
||||||
Logger.printDebug(() -> "Ignoring stale dislikes data for short: " + videoId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update text views that appear to be visible on screen.
|
|
||||||
// Only 1 will be the actual textview for the current Short,
|
|
||||||
// but discarded and not yet garbage collected views can remain.
|
|
||||||
// So must set the dislike span on all views that match.
|
|
||||||
for (WeakReference<TextView> textViewRef : shortsTextViewRefs) {
|
|
||||||
TextView textView = textViewRef.get();
|
|
||||||
if (textView == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (isShortTextViewOnScreen(textView)
|
|
||||||
&& (forceUpdate || textView.getText().toString().equals(SHORTS_LOADING_SPAN.toString()))) {
|
|
||||||
Logger.printDebug(() -> "Setting Shorts TextView to: " + shortsDislikesSpan);
|
|
||||||
textView.setText(shortsDislikesSpan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (videoData.fetchCompleted()) {
|
|
||||||
update.run(); // Network call is completed, no need to wait on background thread.
|
|
||||||
} else {
|
|
||||||
Utils.runOnBackgroundThread(update);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "updateOnScreenShortsTextViews failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a view is within the screen bounds.
|
|
||||||
*/
|
|
||||||
private static boolean isShortTextViewOnScreen(@NonNull View view) {
|
|
||||||
final int[] location = new int[2];
|
|
||||||
view.getLocationInWindow(location);
|
|
||||||
if (location[0] <= 0 && location[1] <= 0) { // Lower bound
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Rect windowRect = new Rect();
|
|
||||||
view.getWindowVisibleDisplayFrame(windowRect); // Upper bound
|
|
||||||
return location[0] < windowRect.width() && location[1] < windowRect.height();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Video Id and voting hooks (all players).
|
// Video Id and voting hooks (all players).
|
||||||
//
|
//
|
||||||
@@ -503,8 +377,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
if (videoIdIsShort && (!isShortAndOpeningOrPlaying || !Settings.RYD_SHORTS.get())) {
|
if (videoIdIsShort && (!isShortAndOpeningOrPlaying || !Settings.RYD_SHORTS.get())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final boolean waitForFetchToComplete = !IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
final boolean waitForFetchToComplete = videoIdIsShort && !lastPlayerResponseWasShort;
|
||||||
&& videoIdIsShort && !lastPlayerResponseWasShort;
|
|
||||||
|
|
||||||
Logger.printDebug(() -> "Prefetching RYD for video: " + videoId);
|
Logger.printDebug(() -> "Prefetching RYD for video: " + videoId);
|
||||||
ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
||||||
@@ -557,12 +430,6 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
data.setVideoIdIsShort(true);
|
data.setVideoIdIsShort(true);
|
||||||
}
|
}
|
||||||
currentVideoData = data;
|
currentVideoData = data;
|
||||||
|
|
||||||
// Current video id hook can be called out of order with the non litho Shorts text view hook.
|
|
||||||
// Must manually update again here.
|
|
||||||
if (isNoneHiddenOrSlidingMinimized) {
|
|
||||||
updateOnScreenShortsTextViews(true);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "newVideoLoaded failure", ex);
|
Logger.printException(() -> "newVideoLoaded failure", ex);
|
||||||
}
|
}
|
||||||
@@ -587,7 +454,10 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
||||||
videoData.setVideoIdIsShort(true);
|
videoData.setVideoIdIsShort(true);
|
||||||
lastLithoShortsVideoData = videoData;
|
lastLithoShortsVideoData = videoData;
|
||||||
lithoShortsShouldUseCurrentData = false;
|
synchronized (ReturnYouTubeDislikePatch.class) {
|
||||||
|
// Use litho Shorts data for the next like and dislike spans.
|
||||||
|
useLithoShortsVideoDataCount = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean videoIdIsSame(@Nullable ReturnYouTubeDislike fetch, @Nullable String videoId) {
|
private static boolean videoIdIsSame(@Nullable ReturnYouTubeDislike fetch, @Nullable String videoId) {
|
||||||
@@ -622,13 +492,6 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
for (Vote v : Vote.values()) {
|
for (Vote v : Vote.values()) {
|
||||||
if (v.value == vote) {
|
if (v.value == vote) {
|
||||||
videoData.sendVote(v);
|
videoData.sendVote(v);
|
||||||
|
|
||||||
if (isNoneHiddenOrMinimized) {
|
|
||||||
if (lastLithoShortsVideoData != null) {
|
|
||||||
lithoShortsShouldUseCurrentData = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -58,7 +57,6 @@ public class ShortsAutoplayPatch {
|
|||||||
/**
|
/**
|
||||||
* @return If the app is currently in background PiP mode.
|
* @return If the app is currently in background PiP mode.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
private static boolean isAppInBackgroundPiPMode() {
|
private static boolean isAppInBackgroundPiPMode() {
|
||||||
Activity activity = mainActivityRef.get();
|
Activity activity = mainActivityRef.get();
|
||||||
return activity != null && activity.isInPictureInPictureMode();
|
return activity != null && activity.isInPictureInPictureMode();
|
||||||
@@ -80,8 +78,7 @@ public class ShortsAutoplayPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
public static Enum<?> changeShortsRepeatBehavior(@Nullable Enum<?> original) {
|
||||||
public static Enum<?> changeShortsRepeatBehavior(Enum<?> original) {
|
|
||||||
try {
|
try {
|
||||||
final boolean autoplay;
|
final boolean autoplay;
|
||||||
|
|
||||||
@@ -103,17 +100,35 @@ public class ShortsAutoplayPatch {
|
|||||||
: ShortsLoopBehavior.REPEAT;
|
: ShortsLoopBehavior.REPEAT;
|
||||||
|
|
||||||
if (behavior.ytEnumValue != null) {
|
if (behavior.ytEnumValue != null) {
|
||||||
Logger.printDebug(() -> behavior.ytEnumValue == original
|
Logger.printDebug(() -> {
|
||||||
? "Changing Shorts repeat behavior from: " + original.name() + " to: " + behavior.ytEnumValue
|
String name = (original == null ? "unknown (null)" : original.name());
|
||||||
: "Behavior setting is same as original. Using original: " + original.name()
|
return behavior == original
|
||||||
);
|
? "Behavior setting is same as original. Using original: " + name
|
||||||
|
: "Changing Shorts repeat behavior from: " + name + " to: " + behavior.name();
|
||||||
|
});
|
||||||
|
|
||||||
return behavior.ytEnumValue;
|
return behavior.ytEnumValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (original == null) {
|
||||||
|
// Cannot return null, as null is used to indicate Short was auto played.
|
||||||
|
// Unpatched app replaces null with unknown enum type (appears to fix for bad api data).
|
||||||
|
Enum<?> unknown = ShortsLoopBehavior.UNKNOWN.ytEnumValue;
|
||||||
|
Logger.printDebug(() -> "Original is null, returning: " + unknown.name());
|
||||||
|
return unknown;
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "changeShortsRepeatState failure", ex);
|
Logger.printException(() -> "changeShortsRepeatBehavior failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean isAutoPlay(Enum<?> original) {
|
||||||
|
return ShortsLoopBehavior.SINGLE_PLAY.ytEnumValue == original;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ package app.revanced.extension.youtube.patches;
|
|||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
public class VersionCheckPatch {
|
public class VersionCheckPatch {
|
||||||
public static final boolean IS_19_17_OR_GREATER = Utils.getAppVersionName().compareTo("19.17.00") >= 0;
|
private static boolean isVersionOrGreater(String version) {
|
||||||
public static final boolean IS_19_20_OR_GREATER = Utils.getAppVersionName().compareTo("19.20.00") >= 0;
|
return Utils.getAppVersionName().compareTo(version) >= 0;
|
||||||
public static final boolean IS_19_21_OR_GREATER = Utils.getAppVersionName().compareTo("19.21.00") >= 0;
|
}
|
||||||
public static final boolean IS_19_26_OR_GREATER = Utils.getAppVersionName().compareTo("19.26.00") >= 0;
|
|
||||||
public static final boolean IS_19_29_OR_GREATER = Utils.getAppVersionName().compareTo("19.29.00") >= 0;
|
public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
|
||||||
public static final boolean IS_19_34_OR_GREATER = Utils.getAppVersionName().compareTo("19.34.00") >= 0;
|
public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
|
||||||
public static final boolean IS_19_46_OR_GREATER = Utils.getAppVersionName().compareTo("19.46.00") >= 0;
|
public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00");
|
||||||
|
public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00");
|
||||||
|
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
|
||||||
|
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
|
||||||
|
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,48 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class WideSearchbarPatch {
|
public final class WideSearchbarPatch {
|
||||||
|
|
||||||
|
private static final Boolean WIDE_SEARCHBAR_ENABLED = Settings.WIDE_SEARCHBAR.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
public static boolean enableWideSearchbar(boolean original) {
|
public static boolean enableWideSearchbar(boolean original) {
|
||||||
return Settings.WIDE_SEARCHBAR.get() || original;
|
return WIDE_SEARCHBAR_ENABLED || original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setActionBar(View view) {
|
||||||
|
try {
|
||||||
|
if (!WIDE_SEARCHBAR_ENABLED) return;
|
||||||
|
|
||||||
|
View searchBarView = Utils.getChildViewByResourceName(view, "search_bar");
|
||||||
|
|
||||||
|
final int paddingLeft = searchBarView.getPaddingLeft();
|
||||||
|
final int paddingRight = searchBarView.getPaddingRight();
|
||||||
|
final int paddingTop = searchBarView.getPaddingTop();
|
||||||
|
final int paddingBottom = searchBarView.getPaddingBottom();
|
||||||
|
final int paddingStart = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
8, Resources.getSystem().getDisplayMetrics());
|
||||||
|
|
||||||
|
if (Utils.isRightToLeftTextLayout()) {
|
||||||
|
searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom);
|
||||||
|
} else {
|
||||||
|
searchBarView.setPadding(paddingStart, paddingTop, paddingRight, paddingBottom);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "setActionBar failure", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ import static app.revanced.extension.youtube.patches.announcements.requests.Anno
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.os.Build;
|
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -31,7 +28,6 @@ public final class AnnouncementsPatch {
|
|||||||
private AnnouncementsPatch() {
|
private AnnouncementsPatch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
private static boolean isLatestAlready() throws IOException {
|
private static boolean isLatestAlready() throws IOException {
|
||||||
HttpURLConnection connection =
|
HttpURLConnection connection =
|
||||||
AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT_IDS);
|
AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT_IDS);
|
||||||
@@ -70,7 +66,6 @@ public final class AnnouncementsPatch {
|
|||||||
return Settings.ANNOUNCEMENT_LAST_ID.get() == id;
|
return Settings.ANNOUNCEMENT_LAST_ID.get() == id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
public static void showAnnouncement(final Activity context) {
|
public static void showAnnouncement(final Activity context) {
|
||||||
if (!Settings.ANNOUNCEMENTS.get()) return;
|
if (!Settings.ANNOUNCEMENTS.get()) return;
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ public final class AdsFilter extends Filter {
|
|||||||
"video_display_button_group_layout",
|
"video_display_button_group_layout",
|
||||||
"landscape_image_wide_button_layout",
|
"landscape_image_wide_button_layout",
|
||||||
"video_display_carousel_button_group_layout",
|
"video_display_carousel_button_group_layout",
|
||||||
|
"video_display_full_buttoned_short_dr_layout",
|
||||||
"compact_landscape_image_layout", // Tablet layout search results.
|
"compact_landscape_image_layout", // Tablet layout search results.
|
||||||
"text_image_no_button_layout" // Tablet layout search results.
|
"text_image_no_button_layout" // Tablet layout search results.
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ package app.revanced.extension.youtube.patches.components;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.youtube.patches.playback.quality.RestoreOldVideoQualityMenuPatch;
|
import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abuse LithoFilter for {@link RestoreOldVideoQualityMenuPatch}.
|
* Abuse LithoFilter for {@link AdvancedVideoQualityMenuPatch}.
|
||||||
*/
|
*/
|
||||||
public final class VideoQualityMenuFilterPatch extends Filter {
|
public final class AdvancedVideoQualityMenuFilter extends Filter {
|
||||||
// Must be volatile or synchronized, as litho filtering runs off main thread
|
// Must be volatile or synchronized, as litho filtering runs off main thread
|
||||||
// and this field is then access from the main thread.
|
// and this field is then access from the main thread.
|
||||||
public static volatile boolean isVideoQualityMenuVisible;
|
public static volatile boolean isVideoQualityMenuVisible;
|
||||||
|
|
||||||
public VideoQualityMenuFilterPatch() {
|
public AdvancedVideoQualityMenuFilter() {
|
||||||
addPathCallbacks(new StringFilterGroup(
|
addPathCallbacks(new StringFilterGroup(
|
||||||
Settings.RESTORE_OLD_VIDEO_QUALITY_MENU,
|
Settings.ADVANCED_VIDEO_QUALITY_MENU,
|
||||||
"quick_quality_sheet_content.eml-js"
|
"quick_quality_sheet_content.eml-js"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,12 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
final class ButtonsFilter extends Filter {
|
final class ButtonsFilter extends Filter {
|
||||||
|
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.eml";
|
||||||
|
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
|
||||||
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
|
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
|
||||||
|
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
|
||||||
|
|
||||||
|
private final StringFilterGroup likeSubscribeGlow;
|
||||||
private final StringFilterGroup actionBarGroup;
|
private final StringFilterGroup actionBarGroup;
|
||||||
private final StringFilterGroup bufferFilterPathGroup;
|
private final StringFilterGroup bufferFilterPathGroup;
|
||||||
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
|
||||||
@@ -20,18 +24,26 @@ final class ButtonsFilter extends Filter {
|
|||||||
addIdentifierCallbacks(actionBarGroup);
|
addIdentifierCallbacks(actionBarGroup);
|
||||||
|
|
||||||
|
|
||||||
|
likeSubscribeGlow = new StringFilterGroup(
|
||||||
|
Settings.DISABLE_LIKE_SUBSCRIBE_GLOW,
|
||||||
|
"animated_button_border.eml"
|
||||||
|
);
|
||||||
|
|
||||||
bufferFilterPathGroup = new StringFilterGroup(
|
bufferFilterPathGroup = new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
"|ContainerType|button.eml|"
|
"|ContainerType|button.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
|
likeSubscribeGlow,
|
||||||
|
bufferFilterPathGroup,
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
Settings.HIDE_LIKE_DISLIKE_BUTTON,
|
Settings.HIDE_LIKE_DISLIKE_BUTTON,
|
||||||
"|segmented_like_dislike_button"
|
"|segmented_like_dislike_button"
|
||||||
),
|
),
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
Settings.HIDE_DOWNLOAD_BUTTON,
|
Settings.HIDE_DOWNLOAD_BUTTON,
|
||||||
"|download_button.eml|"
|
"|download_button.eml"
|
||||||
),
|
),
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
Settings.HIDE_PLAYLIST_BUTTON,
|
Settings.HIDE_PLAYLIST_BUTTON,
|
||||||
@@ -39,9 +51,8 @@ final class ButtonsFilter extends Filter {
|
|||||||
),
|
),
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
Settings.HIDE_CLIP_BUTTON,
|
Settings.HIDE_CLIP_BUTTON,
|
||||||
"|clip_button.eml|"
|
"|clip_button.eml"
|
||||||
),
|
)
|
||||||
bufferFilterPathGroup
|
|
||||||
);
|
);
|
||||||
|
|
||||||
bufferButtonsGroupList.addAll(
|
bufferButtonsGroupList.addAll(
|
||||||
@@ -57,15 +68,19 @@ final class ButtonsFilter extends Filter {
|
|||||||
Settings.HIDE_REMIX_BUTTON,
|
Settings.HIDE_REMIX_BUTTON,
|
||||||
"yt_outline_youtube_shorts_plus"
|
"yt_outline_youtube_shorts_plus"
|
||||||
),
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_THANKS_BUTTON,
|
||||||
|
"yt_outline_dollar_sign_heart"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_ASK_BUTTON,
|
||||||
|
"yt_fill_spark"
|
||||||
|
),
|
||||||
// Check for clip button both here and using a path filter,
|
// Check for clip button both here and using a path filter,
|
||||||
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_CLIP_BUTTON,
|
Settings.HIDE_CLIP_BUTTON,
|
||||||
"yt_outline_scissors"
|
"yt_outline_scissors"
|
||||||
),
|
|
||||||
new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_THANKS_BUTTON,
|
|
||||||
"yt_outline_dollar_sign_heart"
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -83,6 +98,15 @@ final class ButtonsFilter extends Filter {
|
|||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
if (matchedGroup == likeSubscribeGlow) {
|
||||||
|
if ((path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
||||||
|
&& path.contains(ANIMATED_VECTOR_TYPE_PATH)) {
|
||||||
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// If the current matched group is the action bar group,
|
// If the current matched group is the action bar group,
|
||||||
// in case every filter group is enabled, hide the action bar.
|
// in case every filter group is enabled, hide the action bar.
|
||||||
if (matchedGroup == actionBarGroup) {
|
if (matchedGroup == actionBarGroup) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user