mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-04-30 06:34:27 +02:00
chore: Merge branch dev
to main
(#603)
This commit is contained in:
commit
d03edb2750
173
CHANGELOG.md
173
CHANGELOG.md
@ -1,3 +1,176 @@
|
|||||||
|
# [1.8.0-dev.20](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.19...v1.8.0-dev.20) (2024-04-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Swipe controls:** Save and restore brightness and add auto-brightness toggle ([#610](https://github.com/ReVanced/revanced-integrations/issues/610)) ([1c8e2b2](https://github.com/ReVanced/revanced-integrations/commit/1c8e2b29410048a352cb6aad3dd02773459f91a0))
|
||||||
|
|
||||||
|
# [1.8.0-dev.19](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.18...v1.8.0-dev.19) (2024-04-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide keyword content:** Correctly hide content in the subscription tab ([c3bfa77](https://github.com/ReVanced/revanced-integrations/commit/c3bfa77d62b15dedfed8f697583f2f0805f0c2c1))
|
||||||
|
|
||||||
|
# [1.8.0-dev.18](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.17...v1.8.0-dev.18) (2024-04-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof device dimensions:** Warn about potential performance issues ([#617](https://github.com/ReVanced/revanced-integrations/issues/617)) ([786ac9d](https://github.com/ReVanced/revanced-integrations/commit/786ac9d2b71886964454fcb748e656d1beed1964))
|
||||||
|
|
||||||
|
# [1.8.0-dev.17](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.16...v1.8.0-dev.17) (2024-04-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Hide paid promotion label ([3ce100c](https://github.com/ReVanced/revanced-integrations/commit/3ce100ced57d7099c2209d9a955484f1e7d418e0))
|
||||||
|
|
||||||
|
# [1.8.0-dev.16](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.15...v1.8.0-dev.16) (2024-04-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Hide horizontal tile shelves ([ba30869](https://github.com/ReVanced/revanced-integrations/commit/ba308690cf83067d3ddd54622eebcbd14bc15ac8))
|
||||||
|
|
||||||
|
# [1.8.0-dev.15](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.14...v1.8.0-dev.15) (2024-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add 'About' preference to settings menu ([#608](https://github.com/ReVanced/revanced-integrations/issues/608)) ([b8f260e](https://github.com/ReVanced/revanced-integrations/commit/b8f260ebd3e7c2dc50a57cd060b76f2e0fc4a89c))
|
||||||
|
|
||||||
|
# [1.8.0-dev.14](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.13...v1.8.0-dev.14) (2024-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Hide subscribe button in channel bar ([9938bbf](https://github.com/ReVanced/revanced-integrations/commit/9938bbf0de9592db015ae0cfea83e855e12f0c7e))
|
||||||
|
|
||||||
|
# [1.8.0-dev.13](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.12...v1.8.0-dev.13) (2024-04-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Hide suggested actions in incognito mode ([bba421d](https://github.com/ReVanced/revanced-integrations/commit/bba421ddb63597bf918ecccacfd4a33493016b9f))
|
||||||
|
|
||||||
|
# [1.8.0-dev.12](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.11...v1.8.0-dev.12) (2024-04-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** rename `Hide paid content` to `Hide paid promotion label` ([#616](https://github.com/ReVanced/revanced-integrations/issues/616)) ([13dc172](https://github.com/ReVanced/revanced-integrations/commit/13dc17288d13d024a3fbe318ee0fb23a0d46af85))
|
||||||
|
|
||||||
|
# [1.8.0-dev.11](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.10...v1.8.0-dev.11) (2024-04-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Return YouTube Dislike:** Do not show error toast if API success response contains new lines ([#612](https://github.com/ReVanced/revanced-integrations/issues/612)) ([9108205](https://github.com/ReVanced/revanced-integrations/commit/9108205445c533550db454731d4f9460a3241a03))
|
||||||
|
|
||||||
|
# [1.8.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.9...v1.8.0-dev.10) (2024-04-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Hide tagged products, hide search suggestions ([#615](https://github.com/ReVanced/revanced-integrations/issues/615)) ([0586fb7](https://github.com/ReVanced/revanced-integrations/commit/0586fb70e347c25742e03102441cfb37315b5937))
|
||||||
|
|
||||||
|
# [1.8.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.8...v1.8.0-dev.9) (2024-04-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Use correct hide playables setting key ([a2b1543](https://github.com/ReVanced/revanced-integrations/commit/a2b15433cffec082394a50d14f7eef625a6351c1))
|
||||||
|
|
||||||
|
# [1.8.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.7...v1.8.0-dev.8) (2024-04-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Hide playables ([d6cd550](https://github.com/ReVanced/revanced-integrations/commit/d6cd550880596de5cd2eb4a0d1325a73326d4af9))
|
||||||
|
|
||||||
|
# [1.8.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.6...v1.8.0-dev.7) (2024-04-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Hide `Shop`, `Location` and `Save sound to playlist` buttons ([#614](https://github.com/ReVanced/revanced-integrations/issues/614)) ([acfa3c9](https://github.com/ReVanced/revanced-integrations/commit/acfa3c98868b6d84572ee682ad806a0282ac6dad))
|
||||||
|
|
||||||
|
# [1.8.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.5...v1.8.0-dev.6) (2024-04-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Do not show Shorts suggestions in video player, if all hide Shorts options are enabled ([#613](https://github.com/ReVanced/revanced-integrations/issues/613)) ([c132670](https://github.com/ReVanced/revanced-integrations/commit/c132670400e6bdf17c46b8d04d579fb49c3d2749))
|
||||||
|
|
||||||
|
# [1.8.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.4...v1.8.0-dev.5) (2024-04-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Return YouTube Dislike:** Do not clip compact text when not using English ([eeaeb49](https://github.com/ReVanced/revanced-integrations/commit/eeaeb49f2a562d2690dae184153c303a5b1c4368))
|
||||||
|
|
||||||
|
# [1.8.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.3...v1.8.0-dev.4) (2024-04-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Correctly hide Shorts if navigation tab is changed using device back button ([#611](https://github.com/ReVanced/revanced-integrations/issues/611)) ([ffc3437](https://github.com/ReVanced/revanced-integrations/commit/ffc3437843c24af255d2a0dda9930d2843cac4b6))
|
||||||
|
|
||||||
|
# [1.8.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.2...v1.8.0-dev.3) (2024-04-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Settings:** Do not show a toast if migrating old unknown settings ([f2e15a2](https://github.com/ReVanced/revanced-integrations/commit/f2e15a2e1ff59ae7780cfbd366e5165f4e2b191d))
|
||||||
|
|
||||||
|
# [1.8.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.1...v1.8.0-dev.2) (2024-04-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Do not hide playlist shelf in library ([c5d38a7](https://github.com/ReVanced/revanced-integrations/commit/c5d38a7e0791ebb8fe59397fff959cc94e0a7aed))
|
||||||
|
|
||||||
|
# [1.8.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.7.1-dev.4...v1.8.0-dev.1) (2024-04-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - GmsCore support:** Prompt to disable battery optimizations, if not done already ([#601](https://github.com/ReVanced/revanced-integrations/issues/601)) ([c5c9de5](https://github.com/ReVanced/revanced-integrations/commit/c5c9de500d8f1268799e55c31c446bfe8336f79a))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Add option to hide horizontal shelves ([#598](https://github.com/ReVanced/revanced-integrations/issues/598)) ([fedace0](https://github.com/ReVanced/revanced-integrations/commit/fedace02fd5c443ef37dcf77253438b041f4c3f9))
|
||||||
|
|
||||||
|
## [1.7.1-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.7.1-dev.3...v1.7.1-dev.4) (2024-04-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide load more button:** Include patch with `Hide layout components`, and hide button only in search feed ([#600](https://github.com/ReVanced/revanced-integrations/issues/600)) ([c420891](https://github.com/ReVanced/revanced-integrations/commit/c420891e3ef134f30af79cf2f30da3fa2fe5a455))
|
||||||
|
|
||||||
|
## [1.7.1-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.7.1-dev.2...v1.7.1-dev.3) (2024-04-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Player flyout menu:** Add hide Lock screen menu ([#609](https://github.com/ReVanced/revanced-integrations/issues/609)) ([b2fe105](https://github.com/ReVanced/revanced-integrations/commit/b2fe105199d4a5958676cbc8f9c701541e8ff24a))
|
||||||
|
|
||||||
|
## [1.7.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.7.1-dev.1...v1.7.1-dev.2) (2024-04-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Navigation bar hook:** Handle if search is active but hidden behind a maximized player ([cbccb46](https://github.com/ReVanced/revanced-integrations/commit/cbccb46e639003adbed941f9b88c41b4c9998729))
|
||||||
|
|
||||||
|
## [1.7.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.7.0...v1.7.1-dev.1) (2024-04-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Disable suggested video end screen:** Require app restart ([38ae5aa](https://github.com/ReVanced/revanced-integrations/commit/38ae5aac845745824218a08053db519a3325d7a9))
|
||||||
|
|
||||||
# [1.7.0](https://github.com/ReVanced/revanced-integrations/compare/v1.6.0...v1.7.0) (2024-03-30)
|
# [1.7.0](https://github.com/ReVanced/revanced-integrations/compare/v1.6.0...v1.7.0) (2024-03-30)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
package app.revanced.integrations.shared;
|
package app.revanced.integrations.shared;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import static app.revanced.integrations.shared.StringRef.str;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @noinspection unused
|
* @noinspection unused
|
||||||
*/
|
*/
|
||||||
@ -45,26 +49,19 @@ public class GmsCoreSupport {
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showToastOrDialog(Context context, String toastMessageKey, String dialogMessageKey, String link) {
|
private static void showBatteryOptimizationDialog(Activity context,
|
||||||
if (!(context instanceof Activity)) {
|
String dialogMessageRef,
|
||||||
// Context is for the application and cannot show a dialog using it.
|
String positiveButtonStringRef,
|
||||||
Utils.showToastLong(str(toastMessageKey));
|
DialogInterface.OnClickListener onPositiveClickListener) {
|
||||||
open(link);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use a delay to allow the activity to finish initializing.
|
// Use a delay to allow the activity to finish initializing.
|
||||||
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
new AlertDialog.Builder(context)
|
new AlertDialog.Builder(context)
|
||||||
.setIconAttribute(android.R.attr.alertDialogIcon)
|
.setIconAttribute(android.R.attr.alertDialogIcon)
|
||||||
.setTitle(str("gms_core_dialog_title"))
|
.setTitle(str("gms_core_dialog_title"))
|
||||||
.setMessage(str(dialogMessageKey))
|
.setMessage(str(dialogMessageRef))
|
||||||
.setPositiveButton(str("gms_core_dialog_ok_button_text"), (dialog, id) -> {
|
.setPositiveButton(str(positiveButtonStringRef), onPositiveClickListener)
|
||||||
open(link);
|
// Allow using back button to skip the action, just in case the check can never be satisfied.
|
||||||
})
|
|
||||||
// Manually allow using the back button to dismiss the dialog with the back button,
|
|
||||||
// if troubleshooting and somehow the GmsCore verification checks always fail.
|
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.show();
|
.show();
|
||||||
}, 100);
|
}, 100);
|
||||||
@ -74,47 +71,62 @@ public class GmsCoreSupport {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public static void checkGmsCore(Context context) {
|
public static void checkGmsCore(Activity context) {
|
||||||
try {
|
try {
|
||||||
// Verify GmsCore is installed.
|
// Verify GmsCore is installed.
|
||||||
try {
|
try {
|
||||||
PackageManager manager = context.getPackageManager();
|
PackageManager manager = context.getPackageManager();
|
||||||
manager.getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
|
manager.getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
|
||||||
} catch (PackageManager.NameNotFoundException exception) {
|
} catch (PackageManager.NameNotFoundException exception) {
|
||||||
Logger.printDebug(() -> "GmsCore was not found");
|
Logger.printInfo(() -> "GmsCore was not found");
|
||||||
// Cannot show a dialog and must show a toast,
|
// Cannot show a dialog and must show a toast,
|
||||||
// because on some installations the app crashes before the dialog can display.
|
// because on some installations the app crashes before a dialog can be displayed.
|
||||||
Utils.showToastLong(str("gms_core_toast_not_installed_message"));
|
Utils.showToastLong(str("gms_core_toast_not_installed_message"));
|
||||||
open(getGmsCoreDownload());
|
open(getGmsCoreDownload());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if GmsCore is whitelisted from battery optimizations.
|
|
||||||
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
|
||||||
if (!powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME)) {
|
|
||||||
Logger.printDebug(() -> "GmsCore is not whitelisted from battery optimizations");
|
|
||||||
showToastOrDialog(context,
|
|
||||||
"gms_core_toast_not_whitelisted_message",
|
|
||||||
"gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
|
|
||||||
DONT_KILL_MY_APP_LINK);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if GmsCore is running in the background.
|
// Check if GmsCore is running in the background.
|
||||||
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.printDebug(() -> "GmsCore is not running in the background");
|
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||||
showToastOrDialog(context,
|
|
||||||
"gms_core_toast_not_whitelisted_message",
|
showBatteryOptimizationDialog(context,
|
||||||
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
||||||
DONT_KILL_MY_APP_LINK);
|
"gms_core_dialog_open_website_text",
|
||||||
|
(dialog, id) -> open(DONT_KILL_MY_APP_LINK));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if GmsCore is whitelisted from battery optimizations.
|
||||||
|
if (batteryOptimizationsEnabled(context)) {
|
||||||
|
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
|
||||||
|
showBatteryOptimizationDialog(context,
|
||||||
|
"gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
|
||||||
|
"gms_core_dialog_continue_text",
|
||||||
|
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "checkGmsCore failure", ex);
|
Logger.printException(() -> "checkGmsCore failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("BatteryLife") // Permission is part of GmsCore
|
||||||
|
private static void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
|
intent.setData(Uri.fromParts("package", GMS_CORE_PACKAGE_NAME, null));
|
||||||
|
activity.startActivityForResult(intent, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If GmsCore is not whitelisted from battery optimizations.
|
||||||
|
*/
|
||||||
|
private static boolean batteryOptimizationsEnabled(Context context) {
|
||||||
|
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
private static String getGmsCoreDownload() {
|
private static String getGmsCoreDownload() {
|
||||||
final var vendorGroupId = getGmsCoreVendorGroupId();
|
final var vendorGroupId = getGmsCoreVendorGroupId();
|
||||||
//noinspection SwitchStatementWithTooFewBranches
|
//noinspection SwitchStatementWithTooFewBranches
|
||||||
|
@ -35,6 +35,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.settings.BooleanSetting;
|
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||||
|
import app.revanced.integrations.shared.settings.preference.ReVancedAboutPreference;
|
||||||
import kotlin.text.Regex;
|
import kotlin.text.Regex;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
@ -47,30 +48,44 @@ public class Utils {
|
|||||||
private Utils() {
|
private Utils() {
|
||||||
} // utility class
|
} // utility class
|
||||||
|
|
||||||
public static String getVersionName() {
|
/**
|
||||||
if (versionName != null) return versionName;
|
* Injection point.
|
||||||
|
*
|
||||||
|
* @return The manifest 'Version' entry of the patches.jar used during patching.
|
||||||
|
*/
|
||||||
|
public static String getPatchesReleaseVersion() {
|
||||||
|
return ""; // Value is replaced during patching.
|
||||||
|
}
|
||||||
|
|
||||||
PackageInfo packageInfo;
|
/**
|
||||||
|
* @return The version name of the app, such as "YouTube".
|
||||||
|
*/
|
||||||
|
public static String getAppVersionName() {
|
||||||
|
if (versionName == null) {
|
||||||
try {
|
try {
|
||||||
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
||||||
|
|
||||||
PackageManager packageManager = context.getPackageManager();
|
PackageManager packageManager = context.getPackageManager();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
PackageInfo packageInfo;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
packageInfo = packageManager.getPackageInfo(
|
packageInfo = packageManager.getPackageInfo(
|
||||||
packageName,
|
packageName,
|
||||||
PackageManager.PackageInfoFlags.of(0)
|
PackageManager.PackageInfoFlags.of(0)
|
||||||
);
|
);
|
||||||
else
|
} else {
|
||||||
packageInfo = packageManager.getPackageInfo(
|
packageInfo = packageManager.getPackageInfo(
|
||||||
packageName,
|
packageName,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
}
|
||||||
Logger.printException(() -> "Failed to get package info", e);
|
versionName = packageInfo.versionName;
|
||||||
return null;
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Failed to get package info", ex);
|
||||||
|
versionName = "Unknown";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return versionName = packageInfo.versionName;
|
return versionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,6 +200,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getResourceColor(@NonNull String resourceIdentifierName) throws Resources.NotFoundException {
|
public static int getResourceColor(@NonNull String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
|
//noinspection deprecation
|
||||||
return getContext().getResources().getColor(getResourceIdentifier(resourceIdentifierName, "color"));
|
return getContext().getResources().getColor(getResourceIdentifier(resourceIdentifierName, "color"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +339,7 @@ public class Utils {
|
|||||||
try {
|
try {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> runnable.getClass() + ": " + ex.getMessage(), ex);
|
Logger.printException(() -> runnable.getClass().getSimpleName() + ": " + ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
new Handler(Looper.getMainLooper()).postDelayed(loggingRunnable, delayMillis);
|
new Handler(Looper.getMainLooper()).postDelayed(loggingRunnable, delayMillis);
|
||||||
@ -445,11 +461,8 @@ public class Utils {
|
|||||||
this.keySuffix = keySuffix;
|
this.keySuffix = keySuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Defaults to {@link #UNSORTED} if key is null or has no sort suffix.
|
|
||||||
*/
|
|
||||||
@NonNull
|
@NonNull
|
||||||
static Sort fromKey(@Nullable String key) {
|
static Sort fromKey(@Nullable String key, @NonNull Sort defaultSort) {
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
for (Sort sort : values()) {
|
for (Sort sort : values()) {
|
||||||
if (key.endsWith(sort.keySuffix)) {
|
if (key.endsWith(sort.keySuffix)) {
|
||||||
@ -457,7 +470,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return UNSORTED;
|
return defaultSort;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,19 +492,26 @@ public class Utils {
|
|||||||
* If a preference has no key or no {@link Sort} suffix,
|
* If a preference has no key or no {@link Sort} suffix,
|
||||||
* then the preferences are left unsorted.
|
* then the preferences are left unsorted.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public static void sortPreferenceGroups(@NonNull PreferenceGroup group) {
|
public static void sortPreferenceGroups(@NonNull PreferenceGroup group) {
|
||||||
Sort sort = Sort.fromKey(group.getKey());
|
Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED);
|
||||||
SortedMap<String, Preference> preferences = new TreeMap<>();
|
SortedMap<String, Preference> preferences = new TreeMap<>();
|
||||||
|
|
||||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||||
Preference preference = group.getPreference(i);
|
Preference preference = group.getPreference(i);
|
||||||
|
|
||||||
|
final Sort preferenceSort;
|
||||||
if (preference instanceof PreferenceGroup) {
|
if (preference instanceof PreferenceGroup) {
|
||||||
sortPreferenceGroups((PreferenceGroup) preference);
|
sortPreferenceGroups((PreferenceGroup) preference);
|
||||||
|
preferenceSort = groupSort; // Sort value for groups is for it's content, not itself.
|
||||||
|
} else {
|
||||||
|
// Allow individual preferences to set a key sorting.
|
||||||
|
// Used to force a preference to the top or bottom of a group.
|
||||||
|
preferenceSort = Sort.fromKey(preference.getKey(), groupSort);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String sortValue;
|
final String sortValue;
|
||||||
switch (sort) {
|
switch (preferenceSort) {
|
||||||
case BY_TITLE:
|
case BY_TITLE:
|
||||||
sortValue = removePunctuationConvertToLowercase(preference.getTitle());
|
sortValue = removePunctuationConvertToLowercase(preference.getTitle());
|
||||||
break;
|
break;
|
||||||
@ -511,8 +531,9 @@ public class Utils {
|
|||||||
for (Preference pref : preferences.values()) {
|
for (Preference pref : preferences.values()) {
|
||||||
int order = index++;
|
int order = index++;
|
||||||
|
|
||||||
// If the preference is a PreferenceScreen or is an intent preference, move to the top.
|
// Move any screens, intents, and the one off About preference to the top.
|
||||||
if (pref instanceof PreferenceScreen || pref.getIntent() != null) {
|
if (pref instanceof PreferenceScreen || pref instanceof ReVancedAboutPreference
|
||||||
|
|| pref.getIntent() != null) {
|
||||||
// Arbitrary high number.
|
// Arbitrary high number.
|
||||||
order -= 1000;
|
order -= 1000;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import java.util.Objects;
|
|||||||
* All saved JSON text is converted to lowercase to keep the output less obnoxious.
|
* All saved JSON text is converted to lowercase to keep the output less obnoxious.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class EnumSetting<T extends Enum> extends Setting<T> {
|
public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
||||||
public EnumSetting(String key, T defaultValue) {
|
public EnumSetting(String key, T defaultValue) {
|
||||||
super(key, defaultValue);
|
super(key, defaultValue);
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ public class EnumSetting<T extends Enum> extends Setting<T> {
|
|||||||
@NonNull
|
@NonNull
|
||||||
private T getEnumFromString(String enumName) {
|
private T getEnumFromString(String enumName) {
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
for (Enum value : defaultValue.getClass().getEnumConstants()) {
|
for (Enum<?> value : defaultValue.getClass().getEnumConstants()) {
|
||||||
if (value.name().equalsIgnoreCase(enumName)) {
|
if (value.name().equalsIgnoreCase(enumName)) {
|
||||||
// noinspection unchecked
|
// noinspection unchecked
|
||||||
return (T) value;
|
return (T) value;
|
||||||
|
@ -224,6 +224,7 @@ public abstract class Setting<T> {
|
|||||||
if (!oldPrefs.preferences.contains(settingKey)) {
|
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||||
return; // Nothing to do.
|
return; // Nothing to do.
|
||||||
}
|
}
|
||||||
|
|
||||||
Object newValue = setting.get();
|
Object newValue = setting.get();
|
||||||
final Object migratedValue;
|
final Object migratedValue;
|
||||||
if (setting instanceof BooleanSetting) {
|
if (setting instanceof BooleanSetting) {
|
||||||
@ -238,13 +239,17 @@ public abstract class Setting<T> {
|
|||||||
migratedValue = oldPrefs.getString(settingKey, (String) newValue);
|
migratedValue = oldPrefs.getString(settingKey, (String) newValue);
|
||||||
} else {
|
} else {
|
||||||
Logger.printException(() -> "Unknown setting: " + setting);
|
Logger.printException(() -> "Unknown setting: " + setting);
|
||||||
|
// Remove otherwise it'll show a toast on every launch
|
||||||
|
oldPrefs.preferences.edit().remove(settingKey).apply();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
oldPrefs.preferences.edit().remove(settingKey).apply(); // Remove the old setting.
|
oldPrefs.preferences.edit().remove(settingKey).apply(); // Remove the old setting.
|
||||||
if (migratedValue.equals(newValue)) {
|
if (migratedValue.equals(newValue)) {
|
||||||
Logger.printDebug(() -> "Value does not need migrating: " + settingKey);
|
Logger.printDebug(() -> "Value does not need migrating: " + settingKey);
|
||||||
return; // Old value is already equal to the new setting value.
|
return; // Old value is already equal to the new setting value.
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printDebug(() -> "Migrating old preference value into current preference: " + settingKey);
|
Logger.printDebug(() -> "Migrating old preference value into current preference: " + settingKey);
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
setting.save(migratedValue);
|
setting.save(migratedValue);
|
||||||
|
@ -16,10 +16,7 @@ import app.revanced.integrations.shared.settings.Setting;
|
|||||||
|
|
||||||
import static app.revanced.integrations.shared.StringRef.str;
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
/**
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
*
|
|
||||||
*
|
|
||||||
* @noinspection deprecation, DataFlowIssue , unused */
|
|
||||||
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
/**
|
/**
|
||||||
* Indicates that if a preference changes,
|
* Indicates that if a preference changes,
|
||||||
|
@ -15,7 +15,7 @@ import app.revanced.integrations.shared.Utils;
|
|||||||
|
|
||||||
import static app.revanced.integrations.shared.StringRef.str;
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
/** @noinspection deprecation, unused */
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
||||||
|
|
||||||
private String existingSettings;
|
private String existingSettings;
|
||||||
|
@ -0,0 +1,312 @@
|
|||||||
|
package app.revanced.integrations.shared.settings.preference;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
import static app.revanced.integrations.youtube.requests.Route.Method.GET;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.youtube.requests.Requester;
|
||||||
|
import app.revanced.integrations.youtube.requests.Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a dialog showing the links from {@link SocialLinksRoutes}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
|
public class ReVancedAboutPreference extends Preference {
|
||||||
|
|
||||||
|
private static String useNonBreakingHyphens(String text) {
|
||||||
|
// Replace any dashes with non breaking dashes, so the English text 'pre-release'
|
||||||
|
// and the dev release number does not break and cover two lines.
|
||||||
|
return text.replace("-", "‑"); // #8209 = non breaking hyphen.
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getColorHexString(int color) {
|
||||||
|
return String.format("#%06X", (0x00FFFFFF & color));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isDarkModeEnabled() {
|
||||||
|
Configuration config = getContext().getResources().getConfiguration();
|
||||||
|
final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||||
|
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses can override this and provide a themed color.
|
||||||
|
*/
|
||||||
|
protected int getLightColor() {
|
||||||
|
return Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses can override this and provide a themed color.
|
||||||
|
*/
|
||||||
|
protected int getDarkColor() {
|
||||||
|
return Color.BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createDialogHtml(ReVancedSocialLink[] socialLinks) {
|
||||||
|
final boolean isNetworkConnected = Utils.isNetworkConnected();
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("<html>");
|
||||||
|
builder.append("<body style=\"text-align: center; padding: 10px;\">");
|
||||||
|
|
||||||
|
final boolean isDarkMode = isDarkModeEnabled();
|
||||||
|
String backgroundColorHex = getColorHexString(isDarkMode ? getDarkColor() : getLightColor());
|
||||||
|
String foregroundColorHex = getColorHexString(isDarkMode ? getLightColor() : getDarkColor());
|
||||||
|
// Apply light/dark mode colors.
|
||||||
|
builder.append(String.format(
|
||||||
|
"<style> body { background-color: %s; color: %s; } a { color: %s; } </style>",
|
||||||
|
backgroundColorHex, foregroundColorHex, foregroundColorHex));
|
||||||
|
|
||||||
|
if (isNetworkConnected) {
|
||||||
|
builder.append("<img style=\"width: 100px; height: 100px;\" "
|
||||||
|
// Hide the image if it does not load.
|
||||||
|
+ "onerror=\"this.style.display='none';\" "
|
||||||
|
+ "src=\"https://revanced.app/favicon.ico\" />");
|
||||||
|
}
|
||||||
|
|
||||||
|
String patchesVersion = Utils.getPatchesReleaseVersion();
|
||||||
|
|
||||||
|
// Add the title.
|
||||||
|
builder.append("<h1>")
|
||||||
|
.append("ReVanced")
|
||||||
|
.append("</h1>");
|
||||||
|
|
||||||
|
builder.append("<p>")
|
||||||
|
// Replace hyphens with non breaking dashes so the version number does not break lines.
|
||||||
|
.append(useNonBreakingHyphens(str("revanced_settings_about_links_body", patchesVersion)))
|
||||||
|
.append("</p>");
|
||||||
|
|
||||||
|
// Add a disclaimer if using a dev release.
|
||||||
|
if (patchesVersion.contains("dev")) {
|
||||||
|
builder.append("<h3>")
|
||||||
|
// English text 'Pre-release' can break lines.
|
||||||
|
.append(useNonBreakingHyphens(str("revanced_settings_about_links_dev_header")))
|
||||||
|
.append("</h3>");
|
||||||
|
|
||||||
|
builder.append("<p>")
|
||||||
|
.append(str("revanced_settings_about_links_dev_body"))
|
||||||
|
.append("</p>");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("<h2 style=\"margin-top: 30px;\">")
|
||||||
|
.append(str("revanced_settings_about_links_header"))
|
||||||
|
.append("</h2>");
|
||||||
|
|
||||||
|
builder.append("<div>");
|
||||||
|
for (ReVancedSocialLink social : socialLinks) {
|
||||||
|
builder.append("<div style=\"margin-bottom: 20px;\">");
|
||||||
|
builder.append(String.format("<a href=\"%s\">%s</a>", social.url, social.name));
|
||||||
|
builder.append("</div>");
|
||||||
|
}
|
||||||
|
builder.append("</div>");
|
||||||
|
|
||||||
|
builder.append("</body></html>");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
setOnPreferenceClickListener(pref -> {
|
||||||
|
// Show a progress spinner if the social links are not fetched yet.
|
||||||
|
if (!SocialLinksRoutes.hasFetchedLinks() && Utils.isNetworkConnected()) {
|
||||||
|
ProgressDialog progress = new ProgressDialog(getContext());
|
||||||
|
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
|
progress.show();
|
||||||
|
Utils.runOnBackgroundThread(() -> fetchLinksAndShowDialog(progress));
|
||||||
|
} else {
|
||||||
|
// No network call required and can run now.
|
||||||
|
fetchLinksAndShowDialog(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchLinksAndShowDialog(@Nullable ProgressDialog progress) {
|
||||||
|
ReVancedSocialLink[] socialLinks = SocialLinksRoutes.fetchSocialLinks();
|
||||||
|
String htmlDialog = createDialogHtml(socialLinks);
|
||||||
|
|
||||||
|
Utils.runOnMainThreadNowOrLater(() -> {
|
||||||
|
if (progress != null) {
|
||||||
|
progress.dismiss();
|
||||||
|
}
|
||||||
|
new WebViewDialog(getContext(), htmlDialog).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReVancedAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
public ReVancedAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
public ReVancedAboutPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
public ReVancedAboutPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays html content as a dialog. Any links a user taps on are opened in an external browser.
|
||||||
|
*/
|
||||||
|
class WebViewDialog extends Dialog {
|
||||||
|
|
||||||
|
private final String htmlContent;
|
||||||
|
|
||||||
|
public WebViewDialog(@NonNull Context context, @NonNull String htmlContent) {
|
||||||
|
super(context);
|
||||||
|
this.htmlContent = htmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JS required to hide any broken images. No remote javascript is ever loaded.
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
|
||||||
|
WebView webView = new WebView(getContext());
|
||||||
|
webView.getSettings().setJavaScriptEnabled(true);
|
||||||
|
webView.setWebViewClient(new OpenLinksExternallyWebClient());
|
||||||
|
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null);
|
||||||
|
|
||||||
|
setContentView(webView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OpenLinksExternallyWebClient extends WebViewClient {
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Open link failure", ex);
|
||||||
|
}
|
||||||
|
// Dismiss the about dialog using a delay,
|
||||||
|
// otherwise without a delay the UI looks hectic with the dialog dismissing
|
||||||
|
// to show the settings while simultaneously a web browser is opening.
|
||||||
|
Utils.runOnMainThreadDelayed(WebViewDialog.this::dismiss, 500);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReVancedSocialLink {
|
||||||
|
final boolean preferred;
|
||||||
|
final String name;
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
ReVancedSocialLink(JSONObject json) throws JSONException {
|
||||||
|
this(json.getBoolean("preferred"),
|
||||||
|
json.getString("name"),
|
||||||
|
json.getString("url")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReVancedSocialLink(boolean preferred, String name, String url) {
|
||||||
|
this.preferred = preferred;
|
||||||
|
this.name = name;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ReVancedSocialLink{" +
|
||||||
|
"preferred=" + preferred +
|
||||||
|
", name='" + name + '\'' +
|
||||||
|
", url='" + url + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SocialLinksRoutes {
|
||||||
|
/**
|
||||||
|
* Links to use if fetch links api call fails.
|
||||||
|
*/
|
||||||
|
private static final ReVancedSocialLink[] NO_CONNECTION_STATIC_LINKS = {
|
||||||
|
new ReVancedSocialLink(true, "ReVanced.app", "https://revanced.app")
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final String SOCIAL_LINKS_PROVIDER = "https://api.revanced.app/v2";
|
||||||
|
private static final Route.CompiledRoute GET_SOCIAL = new Route(GET, "/socials").compile();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static volatile ReVancedSocialLink[] fetchedLinks;
|
||||||
|
|
||||||
|
static boolean hasFetchedLinks() {
|
||||||
|
return fetchedLinks != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ReVancedSocialLink[] fetchSocialLinks() {
|
||||||
|
try {
|
||||||
|
if (hasFetchedLinks()) return fetchedLinks;
|
||||||
|
|
||||||
|
// Check if there is no internet connection.
|
||||||
|
if (!Utils.isNetworkConnected()) return NO_CONNECTION_STATIC_LINKS;
|
||||||
|
|
||||||
|
HttpURLConnection connection = Requester.getConnectionFromCompiledRoute(SOCIAL_LINKS_PROVIDER, GET_SOCIAL);
|
||||||
|
connection.setConnectTimeout(5000);
|
||||||
|
connection.setReadTimeout(5000);
|
||||||
|
Logger.printDebug(() -> "Fetching social links from: " + connection.getURL());
|
||||||
|
|
||||||
|
// Do not show an exception toast if the server is down
|
||||||
|
final int responseCode = connection.getResponseCode();
|
||||||
|
if (responseCode != 200) {
|
||||||
|
Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode);
|
||||||
|
return NO_CONNECTION_STATIC_LINKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject json = Requester.parseJSONObjectAndDisconnect(connection);
|
||||||
|
JSONArray socials = json.getJSONArray("socials");
|
||||||
|
|
||||||
|
List<ReVancedSocialLink> links = new ArrayList<>();
|
||||||
|
for (int i = 0, length = socials.length(); i < length; i++) {
|
||||||
|
ReVancedSocialLink link = new ReVancedSocialLink(socials.getJSONObject(i));
|
||||||
|
links.add(link);
|
||||||
|
}
|
||||||
|
Logger.printDebug(() -> "links: " + links);
|
||||||
|
|
||||||
|
return fetchedLinks = links.toArray(new ReVancedSocialLink[0]);
|
||||||
|
|
||||||
|
} catch (SocketTimeoutException ex) {
|
||||||
|
Logger.printInfo(() -> "Could not fetch social links", ex); // No toast.
|
||||||
|
} catch (JSONException ex) {
|
||||||
|
Logger.printException(() -> "Could not parse about information", ex);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Failed to get about information", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_CONNECTION_STATIC_LINKS;
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import static app.revanced.integrations.shared.StringRef.str;
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class ResettableEditTextPreference extends EditTextPreference {
|
public class ResettableEditTextPreference extends EditTextPreference {
|
||||||
|
|
||||||
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
@ -33,7 +33,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
super.onPrepareDialogBuilder(builder);
|
super.onPrepareDialogBuilder(builder);
|
||||||
Setting setting = Setting.getSettingFromPath(getKey());
|
Setting<?> setting = Setting.getSettingFromPath(getKey());
|
||||||
if (setting != null) {
|
if (setting != null) {
|
||||||
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||||||
}
|
}
|
||||||
button.setOnClickListener(v -> {
|
button.setOnClickListener(v -> {
|
||||||
try {
|
try {
|
||||||
Setting setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
|
Setting<?> setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
|
||||||
String defaultStringValue = setting.defaultValue.toString();
|
String defaultStringValue = setting.defaultValue.toString();
|
||||||
EditText editText = getEditText();
|
EditText editText = getEditText();
|
||||||
editText.setText(defaultStringValue);
|
editText.setText(defaultStringValue);
|
||||||
|
@ -53,7 +53,7 @@ public class SharedPrefCategory {
|
|||||||
/**
|
/**
|
||||||
* @param value a NULL parameter removes the value from the preferences
|
* @param value a NULL parameter removes the value from the preferences
|
||||||
*/
|
*/
|
||||||
public void saveEnumAsString(@NonNull String key, @Nullable Enum value) {
|
public void saveEnumAsString(@NonNull String key, @Nullable Enum<?> value) {
|
||||||
saveObjectAsString(key, value);
|
saveObjectAsString(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ public class SharedPrefCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public <T extends Enum> T getEnum(@NonNull String key, @NonNull T _default) {
|
public <T extends Enum<?>> T getEnum(@NonNull String key, @NonNull T _default) {
|
||||||
Objects.requireNonNull(_default);
|
Objects.requireNonNull(_default);
|
||||||
try {
|
try {
|
||||||
String enumName = preferences.getString(key, null);
|
String enumName = preferences.getString(key, null);
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
package app.revanced.integrations.youtube;
|
package app.revanced.integrations.youtube;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.graphics.Color;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.integrations.shared.Utils;
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
|
||||||
public class ThemeHelper {
|
public class ThemeHelper {
|
||||||
|
@Nullable
|
||||||
|
private static Integer darkThemeColor, lightThemeColor;
|
||||||
private static int themeValue;
|
private static int themeValue;
|
||||||
|
|
||||||
public static void setTheme(Object value) {
|
/**
|
||||||
final int newOrdinalValue = ((Enum) value).ordinal();
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setTheme(Enum<?> value) {
|
||||||
|
final int newOrdinalValue = value.ordinal();
|
||||||
if (themeValue != newOrdinalValue) {
|
if (themeValue != newOrdinalValue) {
|
||||||
themeValue = newOrdinalValue;
|
themeValue = newOrdinalValue;
|
||||||
Logger.printDebug(() -> "Theme value: " + newOrdinalValue);
|
Logger.printDebug(() -> "Theme value: " + newOrdinalValue);
|
||||||
@ -26,4 +35,48 @@ public class ThemeHelper {
|
|||||||
activity.setTheme(Utils.getResourceIdentifier(theme, "style"));
|
activity.setTheme(Utils.getResourceIdentifier(theme, "style"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
private static String darkThemeResourceName() {
|
||||||
|
// Value is changed by Theme patch, if included.
|
||||||
|
return "@android:color/black";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The dark theme color as specified by the Theme patch (if included),
|
||||||
|
* or the Android color of black.
|
||||||
|
*/
|
||||||
|
public static int getDarkThemeColor() {
|
||||||
|
if (darkThemeColor == null) {
|
||||||
|
darkThemeColor = getColorInt(darkThemeResourceName());
|
||||||
|
}
|
||||||
|
return darkThemeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
private static String lightThemeResourceName() {
|
||||||
|
// Value is changed by Theme patch, if included.
|
||||||
|
return "@android:color/white";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The light theme color as specified by the Theme patch (if included),
|
||||||
|
* or the Android color of white.
|
||||||
|
*/
|
||||||
|
public static int getLightThemeColor() {
|
||||||
|
if (lightThemeColor == null) {
|
||||||
|
lightThemeColor = getColorInt(lightThemeResourceName());
|
||||||
|
}
|
||||||
|
return lightThemeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getColorInt(String colorString) {
|
||||||
|
if (colorString.startsWith("#")) {
|
||||||
|
return Color.parseColor(colorString);
|
||||||
|
}
|
||||||
|
return Utils.getResourceColor(colorString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
package app.revanced.integrations.youtube.patches;
|
package app.revanced.integrations.youtube.patches;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
import static app.revanced.integrations.youtube.settings.Settings.*;
|
||||||
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
|
||||||
import app.revanced.integrations.shared.settings.EnumSetting;
|
|
||||||
import app.revanced.integrations.shared.settings.Setting;
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
import app.revanced.integrations.youtube.shared.NavigationBar;
|
|
||||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
|
||||||
|
|
||||||
import org.chromium.net.UrlRequest;
|
import org.chromium.net.UrlRequest;
|
||||||
import org.chromium.net.UrlResponseInfo;
|
import org.chromium.net.UrlResponseInfo;
|
||||||
import org.chromium.net.impl.CronetUrlRequest;
|
import org.chromium.net.impl.CronetUrlRequest;
|
||||||
@ -26,13 +22,12 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import static app.revanced.integrations.shared.StringRef.str;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_HOME;
|
import app.revanced.integrations.shared.Utils;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_LIBRARY;
|
import app.revanced.integrations.shared.settings.Setting;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_PLAYER;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_SEARCH;
|
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||||
import static app.revanced.integrations.youtube.settings.Settings.ALT_THUMBNAIL_SUBSCRIPTIONS;
|
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternative YouTube thumbnails.
|
* Alternative YouTube thumbnails.
|
||||||
@ -134,11 +129,6 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
*/
|
*/
|
||||||
private static volatile long timeToResumeDeArrowAPICalls;
|
private static volatile long timeToResumeDeArrowAPICalls;
|
||||||
|
|
||||||
/**
|
|
||||||
* Used only for debug logging.
|
|
||||||
*/
|
|
||||||
private static volatile EnumSetting<ThumbnailOption> currentOptionSetting;
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
dearrowApiUri = validateSettings();
|
dearrowApiUri = validateSettings();
|
||||||
final int port = dearrowApiUri.getPort();
|
final int port = dearrowApiUri.getPort();
|
||||||
@ -162,21 +152,38 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
return apiUri;
|
return apiUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EnumSetting<ThumbnailOption> optionSettingForCurrentNavigation() {
|
private static ThumbnailOption optionSettingForCurrentNavigation() {
|
||||||
if (NavigationBar.isSearchBarActive()) { // Must check search first.
|
// Must check player type first, as search bar can be active behind the player.
|
||||||
return ALT_THUMBNAIL_SEARCH;
|
|
||||||
}
|
|
||||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||||
return ALT_THUMBNAIL_PLAYER;
|
return ALT_THUMBNAIL_PLAYER.get();
|
||||||
}
|
}
|
||||||
if (NavigationButton.HOME.isSelected()) {
|
|
||||||
return ALT_THUMBNAIL_HOME;
|
// Must check second, as search can be from any tab.
|
||||||
|
if (NavigationBar.isSearchBarActive()) {
|
||||||
|
return ALT_THUMBNAIL_SEARCH.get();
|
||||||
}
|
}
|
||||||
if (NavigationButton.SUBSCRIPTIONS.isSelected() || NavigationButton.NOTIFICATIONS.isSelected()) {
|
|
||||||
return ALT_THUMBNAIL_SUBSCRIPTIONS;
|
// Avoid checking which navigation button is selected, if all other settings are the same.
|
||||||
|
ThumbnailOption homeOption = ALT_THUMBNAIL_HOME.get();
|
||||||
|
ThumbnailOption subscriptionsOption = ALT_THUMBNAIL_SUBSCRIPTIONS.get();
|
||||||
|
ThumbnailOption libraryOption = ALT_THUMBNAIL_LIBRARY.get();
|
||||||
|
if ((homeOption == subscriptionsOption) && (homeOption == libraryOption)) {
|
||||||
|
return homeOption; // All are the same option.
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||||
|
if (selectedNavButton == null) {
|
||||||
|
// Unknown tab, treat as the home tab;
|
||||||
|
return homeOption;
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.HOME) {
|
||||||
|
return homeOption;
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS || selectedNavButton == NavigationButton.NOTIFICATIONS) {
|
||||||
|
return subscriptionsOption;
|
||||||
}
|
}
|
||||||
// A library tab variant is active.
|
// A library tab variant is active.
|
||||||
return ALT_THUMBNAIL_LIBRARY;
|
return libraryOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -254,14 +261,7 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
*/
|
*/
|
||||||
public static String overrideImageURL(String originalUrl) {
|
public static String overrideImageURL(String originalUrl) {
|
||||||
try {
|
try {
|
||||||
EnumSetting<ThumbnailOption> optionSetting = optionSettingForCurrentNavigation();
|
ThumbnailOption option = optionSettingForCurrentNavigation();
|
||||||
ThumbnailOption option = optionSetting.get();
|
|
||||||
if (BaseSettings.DEBUG.get()) {
|
|
||||||
if (currentOptionSetting != optionSetting) {
|
|
||||||
currentOptionSetting = optionSetting;
|
|
||||||
Logger.printDebug(() -> "Changed to setting: " + optionSetting.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option == ThumbnailOption.ORIGINAL) {
|
if (option == ThumbnailOption.ORIGINAL) {
|
||||||
return originalUrl;
|
return originalUrl;
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
package app.revanced.integrations.youtube.patches;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch;
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class HideBreakingNewsPatch {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When spoofing to app versions 17.31.00 and older, the watch history preview bar uses
|
|
||||||
* the same layout components as the breaking news shelf.
|
|
||||||
*
|
|
||||||
* Breaking news does not appear to be present in these older versions anyways.
|
|
||||||
*/
|
|
||||||
private static final boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory =
|
|
||||||
SpoofAppVersionPatch.isSpoofingToLessThan("18.01.00");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void hideBreakingNews(View view) {
|
|
||||||
if (!Settings.HIDE_BREAKING_NEWS.get()
|
|
||||||
|| isSpoofingOldVersionWithHorizontalCardListWatchHistory) return;
|
|
||||||
Utils.hideViewByLayoutParams(view);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package app.revanced.integrations.youtube.patches;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class HideLoadMoreButtonPatch {
|
|
||||||
public static void hideLoadMoreButton(View view){
|
|
||||||
if(!Settings.HIDE_LOAD_MORE_BUTTON.get()) return;
|
|
||||||
Utils.hideViewByLayoutParams(view);
|
|
||||||
}
|
|
||||||
}
|
|
@ -176,12 +176,6 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
textView.removeTextChangedListener(oldUiTextWatcher);
|
textView.removeTextChangedListener(oldUiTextWatcher);
|
||||||
textView.addTextChangedListener(oldUiTextWatcher);
|
textView.addTextChangedListener(oldUiTextWatcher);
|
||||||
|
|
||||||
/**
|
|
||||||
* If the patch is changed to include the dislikes button as a parameter to this method,
|
|
||||||
* then if the button is already selected the dislikes could be adjusted using
|
|
||||||
* {@link ReturnYouTubeDislike#setUserVote(Vote)}
|
|
||||||
*/
|
|
||||||
|
|
||||||
updateOldUIDislikesTextView();
|
updateOldUIDislikesTextView();
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@ -314,19 +308,25 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
*/
|
*/
|
||||||
public static float onRollingNumberMeasured(String text, float measuredTextWidth) {
|
public static float onRollingNumberMeasured(String text, float measuredTextWidth) {
|
||||||
try {
|
try {
|
||||||
if (Settings.RYD_ENABLED.get() && !Settings.RYD_COMPACT_LAYOUT.get()) {
|
if (Settings.RYD_ENABLED.get()) {
|
||||||
if (ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(text)) {
|
if (ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(text)) {
|
||||||
// +1 pixel is needed for some foreign languages that measure
|
// +1 pixel is needed for some foreign languages that measure
|
||||||
// the text different from what is used for layout (Greek in particular).
|
// the text different from what is used for layout (Greek in particular).
|
||||||
// Probably a bug in Android, but who knows.
|
// Probably a bug in Android, but who knows.
|
||||||
// Single line mode is also used as an additional fix for this issue.
|
// Single line mode is also used as an additional fix for this issue.
|
||||||
return measuredTextWidth + ReturnYouTubeDislike.leftSeparatorBounds.right
|
if (Settings.RYD_COMPACT_LAYOUT.get()) {
|
||||||
+ ReturnYouTubeDislike.leftSeparatorShapePaddingPixels + 1;
|
return measuredTextWidth + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return measuredTextWidth + 1
|
||||||
|
+ ReturnYouTubeDislike.leftSeparatorBounds.right
|
||||||
|
+ ReturnYouTubeDislike.leftSeparatorShapePaddingPixels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "onRollingNumberMeasured failure", ex);
|
Logger.printException(() -> "onRollingNumberMeasured failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return measuredTextWidth;
|
return measuredTextWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,10 +344,12 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
} else {
|
} else {
|
||||||
view.setCompoundDrawables(separator, null, null, null);
|
view.setCompoundDrawables(separator, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disliking can cause the span to grow in size, which is ok and is laid out correctly,
|
// Disliking can cause the span to grow in size, which is ok and is laid out correctly,
|
||||||
// but if the user then removes their dislike the layout will not adjust to the new shorter width.
|
// but if the user then removes their dislike the layout will not adjust to the new shorter width.
|
||||||
// Use a center alignment to take up any extra space.
|
// Use a center alignment to take up any extra space.
|
||||||
view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
|
view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
|
||||||
|
|
||||||
// Single line mode does not clip words if the span is larger than the view bounds.
|
// Single line mode does not clip words if the span is larger than the view bounds.
|
||||||
// The styled span applied to the view should always have the same bounds,
|
// The styled span applied to the view should always have the same bounds,
|
||||||
// but use this feature just in case the measurements are somehow off by a few pixels.
|
// but use this feature just in case the measurements are somehow off by a few pixels.
|
||||||
|
@ -61,7 +61,7 @@ public final class AnnouncementsPatch {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonString = Requester.parseInputStreamAndClose(connection.getInputStream(), false);
|
var jsonString = Requester.parseStringAndDisconnect(connection);
|
||||||
|
|
||||||
|
|
||||||
// Parse the announcement. Fall-back to raw string if it fails.
|
// Parse the announcement. Fall-back to raw string if it fails.
|
||||||
|
@ -112,34 +112,36 @@ final class KeywordContentFilter extends Filter {
|
|||||||
|
|
||||||
private volatile ByteTrieSearch bufferSearch;
|
private volatile ByteTrieSearch bufferSearch;
|
||||||
|
|
||||||
private static void logNavigationState(String state) {
|
private static boolean hideKeywordSettingIsActive() {
|
||||||
// Enable locally to debug filtering. Default off to reduce log spam.
|
// Must check player type first, as search bar can be active behind the player.
|
||||||
final boolean LOG_NAVIGATION_STATE = false;
|
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||||
// noinspection ConstantValue
|
// For now, consider the under video results the same as the home feed.
|
||||||
if (LOG_NAVIGATION_STATE) {
|
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||||
Logger.printDebug(() -> "Navigation state: " + state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hideKeywordSettingIsActive() {
|
// Must check second, as search can be from any tab.
|
||||||
if (NavigationBar.isSearchBarActive()) {
|
if (NavigationBar.isSearchBarActive()) {
|
||||||
// Must check first. Search bar can be active with almost any tab.
|
|
||||||
logNavigationState("Search");
|
|
||||||
return Settings.HIDE_KEYWORD_CONTENT_SEARCH.get();
|
return Settings.HIDE_KEYWORD_CONTENT_SEARCH.get();
|
||||||
} else if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
|
||||||
// For now, consider the under video results the same as the home feed.
|
|
||||||
logNavigationState("Player active");
|
|
||||||
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
|
||||||
} else if (NavigationButton.HOME.isSelected()) {
|
|
||||||
logNavigationState("Home tab");
|
|
||||||
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
|
||||||
} else if (NavigationButton.SUBSCRIPTIONS.isSelected()) {
|
|
||||||
logNavigationState("Subscription tab");
|
|
||||||
return Settings.HIDE_SUBSCRIPTIONS_BUTTON.get();
|
|
||||||
} else {
|
|
||||||
// User is in the Library or Notifications tab.
|
|
||||||
logNavigationState("Ignored tab");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid checking navigation button status if all other settings are off.
|
||||||
|
final boolean hideHome = Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||||
|
final boolean hideSubscriptions = Settings.HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS.get();
|
||||||
|
if (!hideHome && !hideSubscriptions) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||||
|
if (selectedNavButton == null) {
|
||||||
|
return hideHome; // Unknown tab, treat the same as home.
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.HOME) {
|
||||||
|
return hideHome;
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
|
||||||
|
return hideSubscriptions;
|
||||||
|
}
|
||||||
|
// User is in the Library or Notifications tab.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,6 +197,7 @@ final class KeywordContentFilter extends Filter {
|
|||||||
|
|
||||||
private synchronized void parseKeywords() { // Must be synchronized since Litho is multi-threaded.
|
private synchronized void parseKeywords() { // Must be synchronized since Litho is multi-threaded.
|
||||||
String rawKeywords = Settings.HIDE_KEYWORD_CONTENT_PHRASES.get();
|
String rawKeywords = Settings.HIDE_KEYWORD_CONTENT_PHRASES.get();
|
||||||
|
//noinspection StringEquality
|
||||||
if (rawKeywords == lastKeywordPhrasesParsed) {
|
if (rawKeywords == lastKeywordPhrasesParsed) {
|
||||||
Logger.printDebug(() -> "Using previously initialized search");
|
Logger.printDebug(() -> "Using previously initialized search");
|
||||||
return; // Another thread won the race, and search is already initialized.
|
return; // Another thread won the race, and search is already initialized.
|
||||||
@ -265,6 +268,7 @@ final class KeywordContentFilter extends Filter {
|
|||||||
if (!hideKeywordSettingIsActive()) return false;
|
if (!hideKeywordSettingIsActive()) return false;
|
||||||
|
|
||||||
// Field is intentionally compared using reference equality.
|
// Field is intentionally compared using reference equality.
|
||||||
|
//noinspection StringEquality
|
||||||
if (Settings.HIDE_KEYWORD_CONTENT_PHRASES.get() != lastKeywordPhrasesParsed) {
|
if (Settings.HIDE_KEYWORD_CONTENT_PHRASES.get() != lastKeywordPhrasesParsed) {
|
||||||
// User changed the keywords.
|
// User changed the keywords.
|
||||||
parseKeywords();
|
parseKeywords();
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
package app.revanced.integrations.youtube.patches.components;
|
package app.revanced.integrations.youtube.patches.components;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
import app.revanced.integrations.youtube.StringTrieSearch;
|
import app.revanced.integrations.youtube.StringTrieSearch;
|
||||||
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||||
|
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class LayoutComponentsFilter extends Filter {
|
public final class LayoutComponentsFilter extends Filter {
|
||||||
@ -31,6 +37,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
private final StringFilterGroup compactChannelBarInner;
|
private final StringFilterGroup compactChannelBarInner;
|
||||||
private final StringFilterGroup compactChannelBarInnerButton;
|
private final StringFilterGroup compactChannelBarInnerButton;
|
||||||
private final ByteArrayFilterGroup joinMembershipButton;
|
private final ByteArrayFilterGroup joinMembershipButton;
|
||||||
|
private final StringFilterGroup horizontalShelves;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
mixPlaylistsExceptions.addPatterns(
|
mixPlaylistsExceptions.addPatterns(
|
||||||
@ -39,7 +46,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public LayoutComponentsFilter() {
|
public LayoutComponentsFilter() {
|
||||||
exceptions.addPatterns(
|
exceptions.addPatterns(
|
||||||
@ -107,8 +113,8 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"medical_panel"
|
"medical_panel"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var paidContent = new StringFilterGroup(
|
final var paidPromotion = new StringFilterGroup(
|
||||||
Settings.HIDE_PAID_CONTENT,
|
Settings.HIDE_PAID_PROMOTION_LABEL,
|
||||||
"paid_content_overlay"
|
"paid_content_overlay"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -171,6 +177,11 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"fullscreen_related_videos"
|
"fullscreen_related_videos"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final var playables = new StringFilterGroup(
|
||||||
|
Settings.HIDE_PLAYABLES,
|
||||||
|
"horizontal_gaming_shelf.eml"
|
||||||
|
);
|
||||||
|
|
||||||
final var quickActions = new StringFilterGroup(
|
final var quickActions = new StringFilterGroup(
|
||||||
Settings.HIDE_QUICK_ACTIONS,
|
Settings.HIDE_QUICK_ACTIONS,
|
||||||
"quick_actions"
|
"quick_actions"
|
||||||
@ -233,17 +244,25 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"endorsement_header_footer"
|
"endorsement_header_footer"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
horizontalShelves = new StringFilterGroup(
|
||||||
|
Settings.HIDE_HORIZONTAL_SHELVES,
|
||||||
|
"horizontal_video_shelf.eml",
|
||||||
|
"horizontal_shelf.eml",
|
||||||
|
"horizontal_tile_shelf.eml"
|
||||||
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
expandableMetadata,
|
expandableMetadata,
|
||||||
inFeedSurvey,
|
inFeedSurvey,
|
||||||
notifyMe,
|
notifyMe,
|
||||||
channelBar,
|
channelBar,
|
||||||
communityPosts,
|
communityPosts,
|
||||||
paidContent,
|
paidPromotion,
|
||||||
searchResultVideo,
|
searchResultVideo,
|
||||||
latestPosts,
|
latestPosts,
|
||||||
channelWatermark,
|
channelWatermark,
|
||||||
communityGuidelines,
|
communityGuidelines,
|
||||||
|
playables,
|
||||||
quickActions,
|
quickActions,
|
||||||
relatedVideos,
|
relatedVideos,
|
||||||
compactBanner,
|
compactBanner,
|
||||||
@ -259,7 +278,8 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
timedReactions,
|
timedReactions,
|
||||||
imageShelf,
|
imageShelf,
|
||||||
channelMemberShelf,
|
channelMemberShelf,
|
||||||
forYouShelf
|
forYouShelf,
|
||||||
|
horizontalShelves
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,12 +290,15 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
|
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The groups are excluded from the filter due to the exceptions list below.
|
// The groups are excluded from the filter due to the exceptions list below.
|
||||||
// Filter them separately here.
|
// Filter them separately here.
|
||||||
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
|
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
|
||||||
|
{
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
|
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
|
||||||
|
|
||||||
@ -294,6 +317,14 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
// TODO: This also hides the feed Shorts shelf header
|
// TODO: This also hides the feed Shorts shelf header
|
||||||
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
|
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
|
||||||
|
|
||||||
|
if (matchedGroup == horizontalShelves) {
|
||||||
|
if (contentIndex == 0 && hideShelves()) {
|
||||||
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,7 +353,40 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
public static boolean showWatermark() {
|
public static boolean showWatermark() {
|
||||||
return !Settings.HIDE_VIDEO_CHANNEL_WATERMARK.get();
|
return !Settings.HIDE_VIDEO_CHANNEL_WATERMARK.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final boolean HIDE_SHOW_MORE_BUTTON_ENABLED = Settings.HIDE_SHOW_MORE_BUTTON.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void hideShowMoreButton(View view) {
|
||||||
|
if (HIDE_SHOW_MORE_BUTTON_ENABLED
|
||||||
|
&& NavigationBar.isSearchBarActive()
|
||||||
|
// Search bar can be active but behind the player.
|
||||||
|
&& !PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||||
|
Utils.hideViewByLayoutParams(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideShelves() {
|
||||||
|
// If the player is opened while library is selected,
|
||||||
|
// then filter any recommendations below the player.
|
||||||
|
if (PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||||
|
// Or if the search is active while library is selected, then also filter.
|
||||||
|
|| NavigationBar.isSearchBarActive()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check navigation button last.
|
||||||
|
// Only filter if the library tab is not selected.
|
||||||
|
// This check is important as the shelf layout is used for the library tab playlists.
|
||||||
|
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||||
|
return selectedNavButton != null && !selectedNavButton.isLibraryOrYouTab();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,10 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||||||
Settings.HIDE_MORE_INFO_MENU,
|
Settings.HIDE_MORE_INFO_MENU,
|
||||||
"yt_outline_info_circle"
|
"yt_outline_info_circle"
|
||||||
),
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_LOCK_SCREEN_MENU,
|
||||||
|
"yt_outline_lock"
|
||||||
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SPEED_MENU,
|
Settings.HIDE_SPEED_MENU,
|
||||||
"yt_outline_play_arrow_half_circle"
|
"yt_outline_play_arrow_half_circle"
|
||||||
@ -75,15 +79,21 @@ public class PlayerFlyoutMenuItemsFilter 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) {
|
||||||
// Shorts also use this player flyout panel
|
|
||||||
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Only 1 path callback was added, so the matched group must be the overflow menu.
|
// Only 1 path callback was added, so the matched group must be the overflow menu.
|
||||||
if (contentIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
if (contentIndex != 0) {
|
||||||
|
return false; // Overflow menu is always the start of the path.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shorts also use this player flyout panel
|
||||||
|
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
||||||
// Super class handles logging.
|
// Super class handles logging.
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package app.revanced.integrations.youtube.patches.components;
|
package app.revanced.integrations.youtube.patches.components;
|
||||||
|
|
||||||
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
||||||
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@ -16,29 +17,33 @@ import app.revanced.integrations.youtube.shared.PlayerType;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class ShortsFilter extends Filter {
|
public final class ShortsFilter extends Filter {
|
||||||
public static PivotBar pivotBar; // Set by patch.
|
public static PivotBar pivotBar; // Set by patch.
|
||||||
private final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.eml";
|
|
||||||
|
private final static String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.eml";
|
||||||
|
/**
|
||||||
|
* For paid promotion label and subscribe button that appears in the channel bar.
|
||||||
|
*/
|
||||||
|
private final static String REEL_METAPANEL_PATH = "reel_metapanel.eml";
|
||||||
|
|
||||||
private final StringFilterGroup shortsCompactFeedVideoPath;
|
private final StringFilterGroup shortsCompactFeedVideoPath;
|
||||||
private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
|
private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
|
||||||
|
|
||||||
private final StringFilterGroup channelBar;
|
|
||||||
private final StringFilterGroup fullVideoLinkLabel;
|
|
||||||
private final StringFilterGroup videoTitle;
|
|
||||||
private final StringFilterGroup reelSoundMetadata;
|
|
||||||
private final StringFilterGroup subscribeButton;
|
private final StringFilterGroup subscribeButton;
|
||||||
private final StringFilterGroup subscribeButtonPaused;
|
|
||||||
private final StringFilterGroup soundButton;
|
|
||||||
private final StringFilterGroup infoPanel;
|
|
||||||
private final StringFilterGroup joinButton;
|
private final StringFilterGroup joinButton;
|
||||||
|
private final StringFilterGroup paidPromotionButton;
|
||||||
private final StringFilterGroup shelfHeader;
|
private final StringFilterGroup shelfHeader;
|
||||||
|
|
||||||
|
private final StringFilterGroup suggestedAction;
|
||||||
|
private final ByteArrayFilterGroupList suggestedActionsGroupList = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
private final StringFilterGroup actionBar;
|
private final StringFilterGroup actionBar;
|
||||||
private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
public ShortsFilter() {
|
public ShortsFilter() {
|
||||||
|
//
|
||||||
// Identifier components.
|
// Identifier components.
|
||||||
|
//
|
||||||
|
|
||||||
var shorts = new StringFilterGroup(
|
var shortsIdentifiers = new StringFilterGroup(
|
||||||
null, // Setting is based on navigation state.
|
null, // Setting is based on navigation state.
|
||||||
"shorts_shelf",
|
"shorts_shelf",
|
||||||
"inline_shorts",
|
"inline_shorts",
|
||||||
@ -46,6 +51,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
"shorts_video_cell",
|
"shorts_video_cell",
|
||||||
"shorts_pivot_item"
|
"shorts_pivot_item"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Feed Shorts shelf header.
|
// Feed Shorts shelf header.
|
||||||
// Use a different filter group for this pattern, as it requires an additional check after matching.
|
// Use a different filter group for this pattern, as it requires an additional check after matching.
|
||||||
shelfHeader = new StringFilterGroup(
|
shelfHeader = new StringFilterGroup(
|
||||||
@ -53,15 +59,11 @@ public final class ShortsFilter extends Filter {
|
|||||||
"shelf_header.eml"
|
"shelf_header.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Home / subscription feed components.
|
addIdentifierCallbacks(shortsIdentifiers, shelfHeader);
|
||||||
var thanksButton = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SHORTS_THANKS_BUTTON,
|
|
||||||
"suggested_action"
|
|
||||||
);
|
|
||||||
|
|
||||||
addIdentifierCallbacks(shorts, shelfHeader, thanksButton);
|
|
||||||
|
|
||||||
|
//
|
||||||
// Path components.
|
// Path components.
|
||||||
|
//
|
||||||
|
|
||||||
// Shorts that appear in the feed/search when the device is using tablet layout.
|
// Shorts that appear in the feed/search when the device is using tablet layout.
|
||||||
shortsCompactFeedVideoPath = new StringFilterGroup(null, "compact_video.eml");
|
shortsCompactFeedVideoPath = new StringFilterGroup(null, "compact_video.eml");
|
||||||
@ -71,6 +73,41 @@ public final class ShortsFilter extends Filter {
|
|||||||
shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(null, "/frame0.jpg");
|
shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(null, "/frame0.jpg");
|
||||||
|
|
||||||
// Shorts player components.
|
// Shorts player components.
|
||||||
|
StringFilterGroup pausedOverlayButtons = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_PAUSED_OVERLAY_BUTTONS,
|
||||||
|
"shorts_paused_state"
|
||||||
|
);
|
||||||
|
|
||||||
|
StringFilterGroup channelBar = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_CHANNEL_BAR,
|
||||||
|
REEL_CHANNEL_BAR_PATH
|
||||||
|
);
|
||||||
|
|
||||||
|
StringFilterGroup fullVideoLinkLabel = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_FULL_VIDEO_LINK_LABEL,
|
||||||
|
"reel_multi_format_link"
|
||||||
|
);
|
||||||
|
|
||||||
|
StringFilterGroup videoTitle = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_VIDEO_TITLE,
|
||||||
|
"shorts_video_title_item"
|
||||||
|
);
|
||||||
|
|
||||||
|
StringFilterGroup reelSoundMetadata = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_SOUND_METADATA_LABEL,
|
||||||
|
"reel_sound_metadata"
|
||||||
|
);
|
||||||
|
|
||||||
|
StringFilterGroup soundButton = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_SOUND_BUTTON,
|
||||||
|
"reel_pivot_button"
|
||||||
|
);
|
||||||
|
|
||||||
|
StringFilterGroup infoPanel = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_INFO_PANEL,
|
||||||
|
"shorts_info_panel_overview"
|
||||||
|
);
|
||||||
|
|
||||||
joinButton = new StringFilterGroup(
|
joinButton = new StringFilterGroup(
|
||||||
Settings.HIDE_SHORTS_JOIN_BUTTON,
|
Settings.HIDE_SHORTS_JOIN_BUTTON,
|
||||||
"sponsor_button"
|
"sponsor_button"
|
||||||
@ -81,39 +118,9 @@ public final class ShortsFilter extends Filter {
|
|||||||
"subscribe_button"
|
"subscribe_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
subscribeButtonPaused = new StringFilterGroup(
|
paidPromotionButton = new StringFilterGroup(
|
||||||
Settings.HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED,
|
Settings.HIDE_PAID_PROMOTION_LABEL,
|
||||||
"shorts_paused_state"
|
"reel_player_disclosure.eml"
|
||||||
);
|
|
||||||
|
|
||||||
channelBar = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SHORTS_CHANNEL_BAR,
|
|
||||||
REEL_CHANNEL_BAR_PATH
|
|
||||||
);
|
|
||||||
|
|
||||||
fullVideoLinkLabel = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SHORTS_FULL_VIDEO_LINK_LABEL,
|
|
||||||
"reel_multi_format_link"
|
|
||||||
);
|
|
||||||
|
|
||||||
videoTitle = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SHORTS_VIDEO_TITLE,
|
|
||||||
"shorts_video_title_item"
|
|
||||||
);
|
|
||||||
|
|
||||||
reelSoundMetadata = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SHORTS_SOUND_METADATA_LABEL,
|
|
||||||
"reel_sound_metadata"
|
|
||||||
);
|
|
||||||
|
|
||||||
soundButton = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SHORTS_SOUND_BUTTON,
|
|
||||||
"reel_pivot_button"
|
|
||||||
);
|
|
||||||
|
|
||||||
infoPanel = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SHORTS_INFO_PANEL,
|
|
||||||
"shorts_info_panel_overview"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
actionBar = new StringFilterGroup(
|
actionBar = new StringFilterGroup(
|
||||||
@ -121,44 +128,73 @@ public final class ShortsFilter extends Filter {
|
|||||||
"shorts_action_bar"
|
"shorts_action_bar"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
suggestedAction = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"suggested_action.eml"
|
||||||
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
shortsCompactFeedVideoPath,
|
shortsCompactFeedVideoPath, suggestedAction, actionBar, joinButton, subscribeButton,
|
||||||
joinButton, subscribeButton, subscribeButtonPaused,
|
paidPromotionButton, pausedOverlayButtons, channelBar, fullVideoLinkLabel, videoTitle,
|
||||||
channelBar, fullVideoLinkLabel, videoTitle, reelSoundMetadata,
|
reelSoundMetadata, soundButton, infoPanel
|
||||||
soundButton, infoPanel, actionBar
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var shortsLikeButton = new ByteArrayFilterGroup(
|
//
|
||||||
|
// Action buttons
|
||||||
|
//
|
||||||
|
videoActionButtonGroupList.addAll(
|
||||||
|
// This also appears as the path item 'shorts_like_button.eml'
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_LIKE_BUTTON,
|
Settings.HIDE_SHORTS_LIKE_BUTTON,
|
||||||
"shorts_like_button"
|
"reel_like_button",
|
||||||
);
|
"reel_like_toggled_button"
|
||||||
|
),
|
||||||
var shortsDislikeButton = new ByteArrayFilterGroup(
|
// This also appears as the path item 'shorts_dislike_button.eml'
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_DISLIKE_BUTTON,
|
Settings.HIDE_SHORTS_DISLIKE_BUTTON,
|
||||||
"shorts_dislike_button"
|
"reel_dislike_button",
|
||||||
);
|
"reel_dislike_toggled_button"
|
||||||
|
),
|
||||||
var shortsCommentButton = new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
|
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
|
||||||
"reel_comment_button"
|
"reel_comment_button"
|
||||||
);
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
var shortsShareButton = new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_SHORTS_SHARE_BUTTON,
|
Settings.HIDE_SHORTS_SHARE_BUTTON,
|
||||||
"reel_share_button"
|
"reel_share_button"
|
||||||
);
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
var shortsRemixButton = new ByteArrayFilterGroup(
|
|
||||||
Settings.HIDE_SHORTS_REMIX_BUTTON,
|
Settings.HIDE_SHORTS_REMIX_BUTTON,
|
||||||
"reel_remix_button"
|
"reel_remix_button"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
videoActionButtonGroupList.addAll(
|
//
|
||||||
shortsLikeButton,
|
// Suggested actions.
|
||||||
shortsDislikeButton,
|
//
|
||||||
shortsCommentButton,
|
suggestedActionsGroupList.addAll(
|
||||||
shortsShareButton,
|
new ByteArrayFilterGroup(
|
||||||
shortsRemixButton
|
Settings.HIDE_SHORTS_SHOP_BUTTON,
|
||||||
|
"yt_outline_bag_"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_TAGGED_PRODUCTS,
|
||||||
|
// Product buttons show pictures of the products, and does not have any unique icons to identify.
|
||||||
|
// Instead use a unique identifier found in the buffer.
|
||||||
|
"PAproduct_listZ"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_LOCATION_LABEL,
|
||||||
|
"yt_outline_location_point_"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_SAVE_SOUND_BUTTON,
|
||||||
|
"yt_outline_list_add_"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_SEARCH_SUGGESTIONS,
|
||||||
|
"yt_outline_search_"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,15 +202,15 @@ public final class ShortsFilter extends Filter {
|
|||||||
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 (contentType == FilterContentType.PATH) {
|
if (contentType == FilterContentType.PATH) {
|
||||||
// Always filter if matched.
|
if (matchedGroup == subscribeButton || matchedGroup == joinButton || matchedGroup == paidPromotionButton) {
|
||||||
if (matchedGroup == soundButton ||
|
// Selectively filter to avoid false positive filtering of other subscribe/join buttons.
|
||||||
matchedGroup == infoPanel ||
|
if (path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH)) {
|
||||||
matchedGroup == channelBar ||
|
return super.isFiltered(
|
||||||
matchedGroup == fullVideoLinkLabel ||
|
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||||
matchedGroup == videoTitle ||
|
);
|
||||||
matchedGroup == reelSoundMetadata ||
|
}
|
||||||
matchedGroup == subscribeButtonPaused
|
return false;
|
||||||
) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
}
|
||||||
|
|
||||||
if (matchedGroup == shortsCompactFeedVideoPath) {
|
if (matchedGroup == shortsCompactFeedVideoPath) {
|
||||||
if (shouldHideShortsFeedItems() && contentIndex == 0
|
if (shouldHideShortsFeedItems() && contentIndex == 0
|
||||||
@ -186,25 +222,26 @@ public final class ShortsFilter extends Filter {
|
|||||||
|
|
||||||
// Video action buttons (like, dislike, comment, share, remix) have the same path.
|
// Video action buttons (like, dislike, comment, share, remix) have the same path.
|
||||||
if (matchedGroup == actionBar) {
|
if (matchedGroup == actionBar) {
|
||||||
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered(
|
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) {
|
||||||
|
return super.isFiltered(
|
||||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter other path groups from pathFilterGroupList, only when reelChannelBar is visible
|
if (matchedGroup == suggestedAction) {
|
||||||
// to avoid false positives.
|
// Suggested actions can be at the start or in the middle of a path.
|
||||||
if (matchedGroup == subscribeButton ||
|
if (suggestedActionsGroupList.check(protobufBufferArray).isFiltered()) {
|
||||||
matchedGroup == joinButton
|
return super.isFiltered(
|
||||||
) {
|
|
||||||
if (path.startsWith(REEL_CHANNEL_BAR_PATH)) return super.isFiltered(
|
|
||||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||||
); // else, return false.
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
// Feed/search path components.
|
// Feed/search identifier components.
|
||||||
if (matchedGroup == shelfHeader) {
|
if (matchedGroup == shelfHeader) {
|
||||||
// Because the header is used in watch history and possibly other places, check for the index,
|
// Because the header is used in watch history and possibly other places, check for the index,
|
||||||
// which is 0 when the shelf header is used for Shorts.
|
// which is 0 when the shelf header is used for Shorts.
|
||||||
@ -219,14 +256,49 @@ public final class ShortsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldHideShortsFeedItems() {
|
private static boolean shouldHideShortsFeedItems() {
|
||||||
if (NavigationBar.isSearchBarActive()) { // Must check search first.
|
final boolean hideHome = Settings.HIDE_SHORTS_HOME.get();
|
||||||
return Settings.HIDE_SHORTS_SEARCH.get();
|
final boolean hideSubscriptions = Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
||||||
} else if (PlayerType.getCurrent().isMaximizedOrFullscreen()
|
final boolean hideSearch = Settings.HIDE_SHORTS_SEARCH.get();
|
||||||
|| NavigationBar.NavigationButton.HOME.isSelected()) {
|
|
||||||
return Settings.HIDE_SHORTS_HOME.get();
|
if (hideHome && hideSubscriptions && hideSearch) {
|
||||||
} else if (NavigationBar.NavigationButton.SUBSCRIPTIONS.isSelected()) {
|
// Shorts suggestions can load in the background if a video is opened and
|
||||||
return Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
// then immediately minimized before any suggestions are loaded.
|
||||||
|
// In this state the player type will show minimized, which makes it not possible to
|
||||||
|
// distinguish between Shorts suggestions loading in the player and between
|
||||||
|
// scrolling thru search/home/subscription tabs while a player is minimized.
|
||||||
|
//
|
||||||
|
// To avoid this situation for users that never want to show Shorts (all hide Shorts options are enabled)
|
||||||
|
// then hide all Shorts everywhere including the Library history and Library playlists.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must check player type first, as search bar can be active behind the player.
|
||||||
|
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||||
|
// For now, consider the under video results the same as the home feed.
|
||||||
|
return hideHome;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must check second, as search can be from any tab.
|
||||||
|
if (NavigationBar.isSearchBarActive()) {
|
||||||
|
return hideSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid checking navigation button status if all other Shorts should show.
|
||||||
|
if (!hideHome && !hideSubscriptions) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||||
|
if (selectedNavButton == null) {
|
||||||
|
return hideHome; // Unknown tab, treat the same as home.
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.HOME) {
|
||||||
|
return hideHome;
|
||||||
|
}
|
||||||
|
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
|
||||||
|
return hideSubscriptions;
|
||||||
|
}
|
||||||
|
// User must be in the library tab. Don't hide the history or any playlists here.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ final class PlayerRoutes {
|
|||||||
|
|
||||||
JSONObject client = new JSONObject();
|
JSONObject client = new JSONObject();
|
||||||
client.put("clientName", "ANDROID");
|
client.put("clientName", "ANDROID");
|
||||||
client.put("clientVersion", Utils.getVersionName());
|
client.put("clientVersion", Utils.getAppVersionName());
|
||||||
client.put("androidSdkVersion", 34);
|
client.put("androidSdkVersion", 34);
|
||||||
|
|
||||||
context.put("client", client);
|
context.put("client", client);
|
||||||
@ -85,7 +85,7 @@ final class PlayerRoutes {
|
|||||||
|
|
||||||
connection.setRequestProperty(
|
connection.setRequestProperty(
|
||||||
"User-Agent", "com.google.android.youtube/" +
|
"User-Agent", "com.google.android.youtube/" +
|
||||||
Utils.getVersionName() +
|
Utils.getAppVersionName() +
|
||||||
" (Linux; U; Android 12; GB) gzip"
|
" (Linux; U; Android 12; GB) gzip"
|
||||||
);
|
);
|
||||||
connection.setRequestProperty("X-Goog-Api-Format-Version", "2");
|
connection.setRequestProperty("X-Goog-Api-Format-Version", "2");
|
||||||
|
@ -24,7 +24,10 @@ public class Requester {
|
|||||||
String url = apiUrl + route.getCompiledRoute();
|
String url = apiUrl + route.getCompiledRoute();
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
connection.setRequestMethod(route.getMethod().name());
|
connection.setRequestMethod(route.getMethod().name());
|
||||||
connection.setRequestProperty("User-Agent", System.getProperty("http.agent") + "; ReVanced/" + Utils.getVersionName());
|
String agentString = System.getProperty("http.agent")
|
||||||
|
+ "; ReVanced/" + Utils.getAppVersionName()
|
||||||
|
+ " (" + Utils.getPatchesReleaseVersion() + ")";
|
||||||
|
connection.setRequestProperty("User-Agent", agentString);
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
@ -32,35 +35,12 @@ public class Requester {
|
|||||||
/**
|
/**
|
||||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||||
*/
|
*/
|
||||||
public static String parseJson(HttpURLConnection connection) throws IOException {
|
private static String parseInputStreamAndClose(InputStream inputStream) throws IOException {
|
||||||
return parseInputStreamAndClose(connection.getInputStream(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
|
||||||
*
|
|
||||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
|
||||||
*
|
|
||||||
* @see #parseJson(HttpURLConnection)
|
|
||||||
*/
|
|
||||||
public static String parseJsonAndDisconnect(HttpURLConnection connection) throws IOException {
|
|
||||||
String result = parseJson(connection);
|
|
||||||
connection.disconnect();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
|
||||||
*
|
|
||||||
* @param stripNewLineCharacters if newline (\n) characters should be stripped from the InputStream
|
|
||||||
*/
|
|
||||||
public static String parseInputStreamAndClose(InputStream inputStream, boolean stripNewLineCharacters) throws IOException {
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||||
StringBuilder jsonBuilder = new StringBuilder();
|
StringBuilder jsonBuilder = new StringBuilder();
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
jsonBuilder.append(line);
|
jsonBuilder.append(line);
|
||||||
if (!stripNewLineCharacters)
|
|
||||||
jsonBuilder.append("\n");
|
jsonBuilder.append("\n");
|
||||||
}
|
}
|
||||||
return jsonBuilder.toString();
|
return jsonBuilder.toString();
|
||||||
@ -68,36 +48,66 @@ public class Requester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
* Parse the {@link HttpURLConnection} response as a String.
|
||||||
|
* This does not close the url connection. If further requests to this host are unlikely
|
||||||
|
* in the near future, then instead use {@link #parseStringAndDisconnect(HttpURLConnection)}.
|
||||||
*/
|
*/
|
||||||
public static String parseErrorJson(HttpURLConnection connection) throws IOException {
|
public static String parseString(HttpURLConnection connection) throws IOException {
|
||||||
return parseInputStreamAndClose(connection.getErrorStream(), false);
|
return parseInputStreamAndClose(connection.getInputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
* Parse the {@link HttpURLConnection} response as a String, and disconnect.
|
||||||
*
|
*
|
||||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
* <b>Should only be used if other requests to the server in the near future are unlikely</b>
|
||||||
*
|
*
|
||||||
* @see #parseErrorJson(HttpURLConnection)
|
* @see #parseString(HttpURLConnection)
|
||||||
*/
|
*/
|
||||||
public static String parseErrorJsonAndDisconnect(HttpURLConnection connection) throws IOException {
|
public static String parseStringAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||||
String result = parseErrorJson(connection);
|
String result = parseString(connection);
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
* Parse the {@link HttpURLConnection} error stream as a String.
|
||||||
|
* If the server sent no error response data, this returns an empty string.
|
||||||
|
*/
|
||||||
|
public static String parseErrorString(HttpURLConnection connection) throws IOException {
|
||||||
|
InputStream errorStream = connection.getErrorStream();
|
||||||
|
if (errorStream == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return parseInputStreamAndClose(errorStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the {@link HttpURLConnection} error stream as a String, and disconnect.
|
||||||
|
* If the server sent no error response data, this returns an empty string.
|
||||||
|
*
|
||||||
|
* Should only be used if other requests to the server are unlikely in the near future.
|
||||||
|
*
|
||||||
|
* @see #parseErrorString(HttpURLConnection)
|
||||||
|
*/
|
||||||
|
public static String parseErrorStringAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||||
|
String result = parseErrorString(connection);
|
||||||
|
connection.disconnect();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the {@link HttpURLConnection} response into a JSONObject.
|
||||||
|
* This does not close the url connection. If further requests to this host are unlikely
|
||||||
|
* in the near future, then instead use {@link #parseJSONObjectAndDisconnect(HttpURLConnection)}.
|
||||||
*/
|
*/
|
||||||
public static JSONObject parseJSONObject(HttpURLConnection connection) throws JSONException, IOException {
|
public static JSONObject parseJSONObject(HttpURLConnection connection) throws JSONException, IOException {
|
||||||
return new JSONObject(parseJson(connection));
|
return new JSONObject(parseString(connection));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||||
*
|
*
|
||||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
* <b>Should only be used if other requests to the server in the near future are unlikely</b>
|
||||||
*
|
*
|
||||||
* @see #parseJSONObject(HttpURLConnection)
|
* @see #parseJSONObject(HttpURLConnection)
|
||||||
*/
|
*/
|
||||||
@ -109,15 +119,17 @@ public class Requester {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||||
|
* This does not close the url connection. If further requests to this host are unlikely
|
||||||
|
* in the near future, then instead use {@link #parseJSONArrayAndDisconnect(HttpURLConnection)}.
|
||||||
*/
|
*/
|
||||||
public static JSONArray parseJSONArray(HttpURLConnection connection) throws JSONException, IOException {
|
public static JSONArray parseJSONArray(HttpURLConnection connection) throws JSONException, IOException {
|
||||||
return new JSONArray(parseJson(connection));
|
return new JSONArray(parseString(connection));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||||
*
|
*
|
||||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
* <b>Should only be used if other requests to the server in the near future are unlikely</b>
|
||||||
*
|
*
|
||||||
* @see #parseJSONArray(HttpURLConnection)
|
* @see #parseJSONArray(HttpURLConnection)
|
||||||
*/
|
*/
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.integrations.youtube.returnyoutubedislike.requests;
|
package app.revanced.integrations.youtube.returnyoutubedislike.requests;
|
||||||
|
|
||||||
import static app.revanced.integrations.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute;
|
|
||||||
import static app.revanced.integrations.shared.StringRef.str;
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
import static app.revanced.integrations.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute;
|
||||||
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
@ -22,11 +22,11 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
import app.revanced.integrations.youtube.requests.Requester;
|
import app.revanced.integrations.youtube.requests.Requester;
|
||||||
import app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
import app.revanced.integrations.shared.Logger;
|
|
||||||
import app.revanced.integrations.shared.Utils;
|
|
||||||
|
|
||||||
public class ReturnYouTubeDislikeApi {
|
public class ReturnYouTubeDislikeApi {
|
||||||
/**
|
/**
|
||||||
@ -279,7 +279,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
// do not disconnect, the same server connection will likely be used again soon
|
// Do not disconnect, the same server connection will likely be used again soon.
|
||||||
JSONObject json = Requester.parseJSONObject(connection);
|
JSONObject json = Requester.parseJSONObject(connection);
|
||||||
try {
|
try {
|
||||||
RYDVoteData votingData = new RYDVoteData(json);
|
RYDVoteData votingData = new RYDVoteData(json);
|
||||||
@ -377,20 +377,17 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String result = null;
|
|
||||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
result = Requester.parseJson(connection);
|
|
||||||
if (result.equalsIgnoreCase("true")) {
|
|
||||||
Logger.printDebug(() -> "Registration confirmation successful");
|
Logger.printDebug(() -> "Registration confirmation successful");
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
final String resultLog = result == null ? "(no response)" : result;
|
// Something went wrong, might as well disconnect.
|
||||||
|
String response = Requester.parseStringAndDisconnect(connection);
|
||||||
Logger.printInfo(() -> "Failed to confirm registration for user: " + userId
|
Logger.printInfo(() -> "Failed to confirm registration for user: " + userId
|
||||||
+ " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog);
|
+ " solution: " + solution + " responseCode: " + responseCode + " response: '" + response + "''");
|
||||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
||||||
null, true);
|
null, true);
|
||||||
connection.disconnect(); // something went wrong, might as well disconnect
|
|
||||||
} catch (SocketTimeoutException ex) {
|
} catch (SocketTimeoutException ex) {
|
||||||
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
|
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
@ -461,6 +458,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
String solution = solvePuzzle(challenge, difficulty);
|
String solution = solvePuzzle(challenge, difficulty);
|
||||||
return confirmVote(videoId, userId, solution);
|
return confirmVote(videoId, userId, solution);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote
|
Logger.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote
|
||||||
+ " response code was: " + responseCode);
|
+ " response code was: " + responseCode);
|
||||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
||||||
@ -501,20 +499,17 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String result = null;
|
|
||||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
result = Requester.parseJson(connection);
|
|
||||||
if (result.equalsIgnoreCase("true")) {
|
|
||||||
Logger.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
Logger.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
final String resultLog = result == null ? "(no response)" : result;
|
// Something went wrong, might as well disconnect.
|
||||||
|
String response = Requester.parseStringAndDisconnect(connection);
|
||||||
Logger.printInfo(() -> "Failed to confirm vote for video: " + videoId
|
Logger.printInfo(() -> "Failed to confirm vote for video: " + videoId
|
||||||
+ " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog);
|
+ " solution: " + solution + " responseCode: " + responseCode + " response: '" + response + "'");
|
||||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
||||||
null, true);
|
null, true);
|
||||||
connection.disconnect(); // something went wrong, might as well disconnect
|
|
||||||
} catch (SocketTimeoutException ex) {
|
} catch (SocketTimeoutException ex) {
|
||||||
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
|
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
|
@ -49,12 +49,13 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE);
|
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE);
|
||||||
public static final BooleanSetting HIDE_HIDE_LATEST_POSTS = new BooleanSetting("revanced_hide_latest_posts_ads", TRUE);
|
public static final BooleanSetting HIDE_HIDE_LATEST_POSTS = new BooleanSetting("revanced_hide_latest_posts_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_MERCHANDISE_BANNERS = new BooleanSetting("revanced_hide_merchandise_banners", TRUE);
|
public static final BooleanSetting HIDE_MERCHANDISE_BANNERS = new BooleanSetting("revanced_hide_merchandise_banners", TRUE);
|
||||||
public static final BooleanSetting HIDE_PAID_CONTENT = new BooleanSetting("revanced_hide_paid_content_ads", TRUE);
|
public static final BooleanSetting HIDE_PAID_PROMOTION_LABEL = new BooleanSetting("revanced_hide_paid_promotion_label", TRUE);
|
||||||
public static final BooleanSetting HIDE_PRODUCTS_BANNER = new BooleanSetting("revanced_hide_products_banner", TRUE);
|
public static final BooleanSetting HIDE_PRODUCTS_BANNER = new BooleanSetting("revanced_hide_products_banner", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHOPPING_LINKS = new BooleanSetting("revanced_hide_shopping_links", TRUE);
|
public static final BooleanSetting HIDE_SHOPPING_LINKS = new BooleanSetting("revanced_hide_shopping_links", TRUE);
|
||||||
public static final BooleanSetting HIDE_SELF_SPONSOR = new BooleanSetting("revanced_hide_self_sponsor_ads", TRUE);
|
public static final BooleanSetting HIDE_SELF_SPONSOR = new BooleanSetting("revanced_hide_self_sponsor_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_hide_video_ads", TRUE, true);
|
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_hide_video_ads", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_WEB_SEARCH_RESULTS = new BooleanSetting("revanced_hide_web_search_results", TRUE);
|
public static final BooleanSetting HIDE_WEB_SEARCH_RESULTS = new BooleanSetting("revanced_hide_web_search_results", TRUE);
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
|
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
|
||||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_SUBSCRIPTIONS = new EnumSetting<>("revanced_alt_thumbnail_subscription", ThumbnailOption.ORIGINAL);
|
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_SUBSCRIPTIONS = new EnumSetting<>("revanced_alt_thumbnail_subscription", ThumbnailOption.ORIGINAL);
|
||||||
@ -71,12 +72,12 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
||||||
public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE);
|
public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE);
|
||||||
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
||||||
public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE);
|
public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE, true);
|
||||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE);
|
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE);
|
||||||
public static final BooleanSetting HIDE_ALBUM_CARDS = new BooleanSetting("revanced_hide_album_cards", FALSE, true);
|
public static final BooleanSetting HIDE_ALBUM_CARDS = new BooleanSetting("revanced_hide_album_cards", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_ARTIST_CARDS = new BooleanSetting("revanced_hide_artist_cards", FALSE);
|
public static final BooleanSetting HIDE_ARTIST_CARDS = new BooleanSetting("revanced_hide_artist_cards", FALSE);
|
||||||
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
|
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_BREAKING_NEWS = new BooleanSetting("revanced_hide_breaking_news", TRUE, true);
|
public static final BooleanSetting HIDE_HORIZONTAL_SHELVES = new BooleanSetting("revanced_hide_horizontal_shelves", TRUE);
|
||||||
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
|
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
|
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
|
||||||
@ -108,13 +109,15 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SEARCH = new BooleanSetting("revanced_hide_keyword_content_search", FALSE);
|
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SEARCH = new BooleanSetting("revanced_hide_keyword_content_search", FALSE);
|
||||||
public static final StringSetting HIDE_KEYWORD_CONTENT_PHRASES = new StringSetting("revanced_hide_keyword_content_phrases", "",
|
public static final StringSetting HIDE_KEYWORD_CONTENT_PHRASES = new StringSetting("revanced_hide_keyword_content_phrases", "",
|
||||||
parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
|
parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
|
||||||
public static final BooleanSetting HIDE_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE, true);
|
@Deprecated public static final BooleanSetting HIDE_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
||||||
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
||||||
public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE);
|
public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE);
|
||||||
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE);
|
public static final BooleanSetting HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE);
|
||||||
public static final BooleanSetting HIDE_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_preview_comment", FALSE, true);
|
public static final BooleanSetting HIDE_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_preview_comment", FALSE, true);
|
||||||
|
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
||||||
public static final BooleanSetting HIDE_QUICK_ACTIONS = new BooleanSetting("revanced_hide_quick_actions", FALSE);
|
public static final BooleanSetting HIDE_QUICK_ACTIONS = new BooleanSetting("revanced_hide_quick_actions", FALSE);
|
||||||
public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE);
|
public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE);
|
||||||
public static final BooleanSetting HIDE_SEARCH_RESULT_SHELF_HEADER = new BooleanSetting("revanced_hide_search_result_shelf_header", FALSE);
|
public static final BooleanSetting HIDE_SEARCH_RESULT_SHELF_HEADER = new BooleanSetting("revanced_hide_search_result_shelf_header", FALSE);
|
||||||
@ -156,8 +159,12 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_SHORTS_SEARCH = new BooleanSetting("revanced_hide_shorts_search", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_SEARCH = new BooleanSetting("revanced_hide_shorts_search", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_shorts_subscribe_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_shorts_subscribe_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED = new BooleanSetting("revanced_hide_shorts_subscribe_button_paused", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_PAUSED_OVERLAY_BUTTONS = new BooleanSetting("revanced_hide_shorts_paused_overlay_buttons", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_shorts_thanks_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_SHOP_BUTTON = new BooleanSetting("revanced_hide_shorts_shop_button", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_TAGGED_PRODUCTS = new BooleanSetting("revanced_hide_shorts_tagged_products", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_LOCATION_LABEL = new BooleanSetting("revanced_hide_shorts_location_label", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_SAVE_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_save_sound_button", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_SEARCH_SUGGESTIONS = new BooleanSetting("revanced_hide_shorts_search_suggestions", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_LIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_like_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_LIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_like_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE);
|
||||||
@ -198,6 +205,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_HELP_MENU = new BooleanSetting("revanced_hide_player_flyout_help", TRUE);
|
public static final BooleanSetting HIDE_HELP_MENU = new BooleanSetting("revanced_hide_player_flyout_help", TRUE);
|
||||||
public static final BooleanSetting HIDE_SPEED_MENU = new BooleanSetting("revanced_hide_player_flyout_speed", FALSE);
|
public static final BooleanSetting HIDE_SPEED_MENU = new BooleanSetting("revanced_hide_player_flyout_speed", FALSE);
|
||||||
public static final BooleanSetting HIDE_MORE_INFO_MENU = new BooleanSetting("revanced_hide_player_flyout_more_info", TRUE);
|
public static final BooleanSetting HIDE_MORE_INFO_MENU = new BooleanSetting("revanced_hide_player_flyout_more_info", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_LOCK_SCREEN_MENU = new BooleanSetting("revanced_hide_player_flyout_lock_screen", FALSE);
|
||||||
public static final BooleanSetting HIDE_AUDIO_TRACK_MENU = new BooleanSetting("revanced_hide_player_flyout_audio_track", FALSE);
|
public static final BooleanSetting HIDE_AUDIO_TRACK_MENU = new BooleanSetting("revanced_hide_player_flyout_audio_track", FALSE);
|
||||||
public static final BooleanSetting HIDE_WATCH_IN_VR_MENU = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
|
public static final BooleanSetting HIDE_WATCH_IN_VR_MENU = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
|
||||||
|
|
||||||
@ -215,7 +223,8 @@ public class Settings extends BaseSettings {
|
|||||||
parent(SPOOF_SIGNATURE));
|
parent(SPOOF_SIGNATURE));
|
||||||
public static final BooleanSetting SPOOF_STORYBOARD_RENDERER = new BooleanSetting("revanced_spoof_storyboard", TRUE, true,
|
public static final BooleanSetting SPOOF_STORYBOARD_RENDERER = new BooleanSetting("revanced_spoof_storyboard", TRUE, true,
|
||||||
parent(SPOOF_SIGNATURE));
|
parent(SPOOF_SIGNATURE));
|
||||||
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true);
|
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
|
||||||
|
"revanced_spoof_device_dimensions_user_dialog_message");
|
||||||
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
|
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
|
||||||
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@ -240,9 +249,9 @@ public class Settings extends BaseSettings {
|
|||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true,
|
public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true,
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true,
|
public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true, parent(SWIPE_BRIGHTNESS));
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
public static final FloatSetting SWIPE_BRIGHTNESS_VALUE = new FloatSetting("revanced_swipe_brightness_value", -1f);
|
||||||
|
public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", FALSE, true, parent(SWIPE_BRIGHTNESS));
|
||||||
// Debugging
|
// Debugging
|
||||||
/**
|
/**
|
||||||
* When enabled, share the debug logs with care.
|
* When enabled, share the debug logs with care.
|
||||||
@ -383,6 +392,8 @@ public class Settings extends BaseSettings {
|
|||||||
HIDE_SHORTS_SEARCH.save(true);
|
HIDE_SHORTS_SEARCH.save(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migrateOldSettingToNew(HIDE_LOAD_MORE_BUTTON, HIDE_SHOW_MORE_BUTTON);
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import android.util.AttributeSet;
|
|||||||
/**
|
/**
|
||||||
* Allows tapping the DeArrow about preference to open the DeArrow website.
|
* Allows tapping the DeArrow about preference to open the DeArrow website.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class AlternativeThumbnailsAboutDeArrowPreference extends Preference {
|
public class AlternativeThumbnailsAboutDeArrowPreference extends Preference {
|
||||||
{
|
{
|
||||||
setOnPreferenceClickListener(pref -> {
|
setOnPreferenceClickListener(pref -> {
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package app.revanced.integrations.youtube.settings.preference;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.settings.preference.ReVancedAboutPreference;
|
||||||
|
import app.revanced.integrations.youtube.ThemeHelper;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ReVancedYouTubeAboutPreference extends ReVancedAboutPreference {
|
||||||
|
|
||||||
|
public int getLightColor() {
|
||||||
|
return ThemeHelper.getLightThemeColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDarkColor() {
|
||||||
|
return ThemeHelper.getDarkThemeColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
public ReVancedYouTubeAboutPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
}
|
@ -79,10 +79,9 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
|
|||||||
shortsPreference = new SwitchPreference(context);
|
shortsPreference = new SwitchPreference(context);
|
||||||
shortsPreference.setChecked(Settings.RYD_SHORTS.get());
|
shortsPreference.setChecked(Settings.RYD_SHORTS.get());
|
||||||
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
|
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
|
||||||
String shortsSummary = str("revanced_ryd_shorts_summary_on",
|
String shortsSummary = ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
||||||
ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
? str("revanced_ryd_shorts_summary_on")
|
||||||
? ""
|
: str("revanced_ryd_shorts_summary_on_disclaimer");
|
||||||
: "\n\n" + str("revanced_ryd_shorts_summary_disclaimer"));
|
|
||||||
shortsPreference.setSummaryOn(shortsSummary);
|
shortsPreference.setSummaryOn(shortsSummary);
|
||||||
shortsPreference.setSummaryOff(str("revanced_ryd_shorts_summary_off"));
|
shortsPreference.setSummaryOff(str("revanced_ryd_shorts_summary_off"));
|
||||||
shortsPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
shortsPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||||
|
@ -100,10 +100,10 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
|||||||
privateUserId.setEnabled(enabled);
|
privateUserId.setEnabled(enabled);
|
||||||
|
|
||||||
// If the user has a private user id, then include a subtext that mentions not to share it.
|
// If the user has a private user id, then include a subtext that mentions not to share it.
|
||||||
String exportSummarySubText = SponsorBlockSettings.userHasSBPrivateId()
|
String importExportSummary = SponsorBlockSettings.userHasSBPrivateId()
|
||||||
? str("revanced_sb_settings_ie_sum_warning")
|
? str("revanced_sb_settings_ie_sum_warning")
|
||||||
: "";
|
: str("revanced_sb_settings_ie_sum");
|
||||||
importExport.setSummary(str("revanced_sb_settings_ie_sum", exportSummarySubText));
|
importExport.setSummary(importExportSummary);
|
||||||
|
|
||||||
apiUrl.setEnabled(enabled);
|
apiUrl.setEnabled(enabled);
|
||||||
importExport.setEnabled(enabled);
|
importExport.setEnabled(enabled);
|
||||||
|
@ -2,21 +2,29 @@ package app.revanced.integrations.youtube.shared;
|
|||||||
|
|
||||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.Logger;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.integrations.shared.Utils;
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||||
import app.revanced.integrations.youtube.settings.Settings;
|
import app.revanced.integrations.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class NavigationBar {
|
public final class NavigationBar {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Search bar
|
||||||
|
//
|
||||||
|
|
||||||
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
|
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,24 +35,115 @@ public final class NavigationBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the search bar is on screen.
|
* @return If the search bar is on screen. This includes if the player
|
||||||
|
* is on screen and the search results are behind the player (and not visible).
|
||||||
|
* Detecting the search is covered by the player can be done by checking {@link PlayerType#isMaximizedOrFullscreen()}.
|
||||||
*/
|
*/
|
||||||
public static boolean isSearchBarActive() {
|
public static boolean isSearchBarActive() {
|
||||||
View searchbarResults = searchBarResultsRef.get();
|
View searchbarResults = searchBarResultsRef.get();
|
||||||
return searchbarResults != null && searchbarResults.getParent() != null;
|
return searchbarResults != null && searchbarResults.getParent() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Navigation bar buttons
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long to wait for the set nav button latch to be released. Maximum wait time must
|
||||||
|
* be as small as possible while still allowing enough time for the nav bar to update.
|
||||||
|
*
|
||||||
|
* YT calls it's back button handlers out of order,
|
||||||
|
* and litho starts filtering before the navigation bar is updated.
|
||||||
|
*
|
||||||
|
* Fixing this situation and not needlessly waiting requires somehow
|
||||||
|
* detecting if a back button key-press will cause a tab change.
|
||||||
|
*
|
||||||
|
* Typically after pressing the back button, the time between the first litho event and
|
||||||
|
* when the nav button is updated is about 10-20ms. Using 50-100ms here should be enough time
|
||||||
|
* and not noticeable, since YT typically takes 100-200ms (or more) to update the view anyways.
|
||||||
|
*
|
||||||
|
* This issue can also be avoided on a patch by patch basis, by avoiding calls to
|
||||||
|
* {@link NavigationButton#getSelectedNavigationButton()} unless absolutely necessary.
|
||||||
|
*/
|
||||||
|
private static final long LATCH_AWAIT_TIMEOUT_MILLISECONDS = 75;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as a workaround to fix the issue of YT calling back button handlers out of order.
|
||||||
|
* Used to hold calls to {@link NavigationButton#getSelectedNavigationButton()}
|
||||||
|
* until the current navigation button can be determined.
|
||||||
|
*
|
||||||
|
* Only used when the hardware back button is pressed.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static volatile CountDownLatch navButtonLatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of nav button layout views to Enum type.
|
||||||
|
* No synchronization is needed, and this is always accessed from the main thread.
|
||||||
|
*/
|
||||||
|
private static final Map<View, NavigationButton> viewToButtonMap = new WeakHashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// On app startup litho can start before the navigation bar is initialized.
|
||||||
|
// Force it to wait until the nav bar is updated.
|
||||||
|
createNavButtonLatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createNavButtonLatch() {
|
||||||
|
navButtonLatch = new CountDownLatch(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void releaseNavButtonLatch() {
|
||||||
|
CountDownLatch latch = navButtonLatch;
|
||||||
|
if (latch != null) {
|
||||||
|
navButtonLatch = null;
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void waitForNavButtonLatchIfNeeded() {
|
||||||
|
CountDownLatch latch = navButtonLatch;
|
||||||
|
if (latch == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Utils.isCurrentlyOnMainThread()) {
|
||||||
|
// The latch is released from the main thread, and waiting from the main thread will always timeout.
|
||||||
|
// This situation has only been observed when navigating out of a submenu and not changing tabs.
|
||||||
|
// and for that use case the nav bar does not change so it's safe to return here.
|
||||||
|
Logger.printDebug(() -> "Cannot block main thread waiting for nav button. Using last known navbar button status.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Logger.printDebug(() -> "Latch wait started");
|
||||||
|
if (latch.await(LATCH_AWAIT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS)) {
|
||||||
|
// Back button changed the navigation tab.
|
||||||
|
Logger.printDebug(() -> "Latch wait complete");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout occurred, and a normal event when pressing the physical back button
|
||||||
|
// does not change navigation tabs.
|
||||||
|
releaseNavButtonLatch(); // Prevent other threads from waiting for no reason.
|
||||||
|
Logger.printDebug(() -> "Latch wait timed out");
|
||||||
|
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Logger.printException(() -> "Latch wait interrupted failure", ex); // Will never happen.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last YT navigation enum loaded. Not necessarily the active navigation tab.
|
* Last YT navigation enum loaded. Not necessarily the active navigation tab.
|
||||||
|
* Always accessed from the main thread.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static volatile String lastYTNavigationEnumName;
|
private static String lastYTNavigationEnumName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void setLastAppNavigationEnum(@Nullable Enum ytNavigationEnumName) {
|
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
|
||||||
if (ytNavigationEnumName != null) {
|
if (ytNavigationEnumName != null) {
|
||||||
lastYTNavigationEnumName = ytNavigationEnumName.name();
|
lastYTNavigationEnumName = ytNavigationEnumName.name();
|
||||||
}
|
}
|
||||||
@ -56,21 +155,16 @@ public final class NavigationBar {
|
|||||||
public static void navigationTabLoaded(final View navigationButtonGroup) {
|
public static void navigationTabLoaded(final View navigationButtonGroup) {
|
||||||
try {
|
try {
|
||||||
String lastEnumName = lastYTNavigationEnumName;
|
String lastEnumName = lastYTNavigationEnumName;
|
||||||
|
|
||||||
for (NavigationButton button : NavigationButton.values()) {
|
for (NavigationButton button : NavigationButton.values()) {
|
||||||
if (button.ytEnumName.equals(lastEnumName)) {
|
if (button.ytEnumName.equals(lastEnumName)) {
|
||||||
ImageView imageView = Utils.getChildView((ViewGroup) navigationButtonGroup,
|
|
||||||
true, view -> view instanceof ImageView);
|
|
||||||
|
|
||||||
if (imageView != null) {
|
|
||||||
Logger.printDebug(() -> "navigationTabLoaded: " + lastEnumName);
|
Logger.printDebug(() -> "navigationTabLoaded: " + lastEnumName);
|
||||||
|
viewToButtonMap.put(navigationButtonGroup, button);
|
||||||
button.imageViewRef = new WeakReference<>(imageView);
|
|
||||||
navigationTabCreatedCallback(button, navigationButtonGroup);
|
navigationTabCreatedCallback(button, navigationButtonGroup);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Log the unknown tab as exception level, only if debug is enabled.
|
// Log the unknown tab as exception level, only if debug is enabled.
|
||||||
// This is because unknown tabs do no harm, and it's only relevant to developers.
|
// This is because unknown tabs do no harm, and it's only relevant to developers.
|
||||||
if (Settings.DEBUG.get()) {
|
if (Settings.DEBUG.get()) {
|
||||||
@ -98,6 +192,45 @@ public final class NavigationBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void navigationTabSelected(View navButtonImageView, boolean isSelected) {
|
||||||
|
try {
|
||||||
|
if (!isSelected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationButton button = viewToButtonMap.get(navButtonImageView);
|
||||||
|
|
||||||
|
if (button == null) { // An unknown tab was selected.
|
||||||
|
// Show a toast only if debug mode is enabled.
|
||||||
|
if (BaseSettings.DEBUG.get()) {
|
||||||
|
Logger.printException(() -> "Unknown navigation view selected: " + navButtonImageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationButton.selectedNavigationButton = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationButton.selectedNavigationButton = button;
|
||||||
|
Logger.printDebug(() -> "Changed to navigation button: " + button);
|
||||||
|
|
||||||
|
// Release any threads waiting for the selected nav button.
|
||||||
|
releaseNavButtonLatch();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "navigationTabSelected failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void onBackPressed(Activity activity) {
|
||||||
|
Logger.printDebug(() -> "Back button pressed");
|
||||||
|
createNavButtonLatch();
|
||||||
|
}
|
||||||
|
|
||||||
/** @noinspection EmptyMethod*/
|
/** @noinspection EmptyMethod*/
|
||||||
private static void navigationTabCreatedCallback(NavigationButton button, View tabView) {
|
private static void navigationTabCreatedCallback(NavigationButton button, View tabView) {
|
||||||
// Code is added during patching.
|
// Code is added during patching.
|
||||||
@ -108,8 +241,7 @@ public final class NavigationBar {
|
|||||||
SHORTS("TAB_SHORTS"),
|
SHORTS("TAB_SHORTS"),
|
||||||
/**
|
/**
|
||||||
* Create new video tab.
|
* Create new video tab.
|
||||||
*
|
* This tab will never be in a selected state, even if the create video UI is on screen.
|
||||||
* {@link #isSelected()} always returns false, even if the create video UI is on screen.
|
|
||||||
*/
|
*/
|
||||||
CREATE("CREATION_TAB_LARGE"),
|
CREATE("CREATION_TAB_LARGE"),
|
||||||
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS"),
|
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS"),
|
||||||
@ -144,41 +276,43 @@ public final class NavigationBar {
|
|||||||
// The hooked YT code does not use an enum, and a dummy name is used here.
|
// The hooked YT code does not use an enum, and a dummy name is used here.
|
||||||
LIBRARY_YOU("YOU_LIBRARY_DUMMY_PLACEHOLDER_NAME");
|
LIBRARY_YOU("YOU_LIBRARY_DUMMY_PLACEHOLDER_NAME");
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static volatile NavigationButton selectedNavigationButton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This will return null only if the currently selected tab is unknown.
|
||||||
|
* This scenario will only happen if the UI has different tabs due to an A/B user test
|
||||||
|
* or YT abruptly changes the navigation layout for some other reason.
|
||||||
|
*
|
||||||
|
* All code calling this method should handle a null return value.
|
||||||
|
*
|
||||||
|
* <b>Due to issues with how YT processes physical back button events,
|
||||||
|
* this patch uses workarounds that can cause this method to take up to 75ms
|
||||||
|
* if the device back button was recently pressed.</b>
|
||||||
|
*
|
||||||
* @return The active navigation tab.
|
* @return The active navigation tab.
|
||||||
* If the user is in the create new video UI, this returns NULL.
|
* If the user is in the upload video UI, this returns tab that is still visually
|
||||||
|
* selected on screen (whatever tab the user was on before tapping the upload button).
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static NavigationButton getSelectedNavigationButton() {
|
public static NavigationButton getSelectedNavigationButton() {
|
||||||
for (NavigationButton button : values()) {
|
waitForNavButtonLatchIfNeeded();
|
||||||
if (button.isSelected()) return button;
|
return selectedNavigationButton;
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return If the currently selected tab is a 'You' or library type.
|
|
||||||
* Covers all known app states including incognito mode and version spoofing.
|
|
||||||
*/
|
|
||||||
public static boolean libraryOrYouTabIsSelected() {
|
|
||||||
return LIBRARY_YOU.isSelected() || LIBRARY_PIVOT_UNKNOWN.isSelected()
|
|
||||||
|| LIBRARY_OLD_UI.isSelected() || LIBRARY_INCOGNITO.isSelected()
|
|
||||||
|| LIBRARY_LOGGED_OUT.isSelected();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YouTube enum name for this tab.
|
* YouTube enum name for this tab.
|
||||||
*/
|
*/
|
||||||
private final String ytEnumName;
|
private final String ytEnumName;
|
||||||
private volatile WeakReference<ImageView> imageViewRef = new WeakReference<>(null);
|
|
||||||
|
|
||||||
NavigationButton(String ytEnumName) {
|
NavigationButton(String ytEnumName) {
|
||||||
this.ytEnumName = ytEnumName;
|
this.ytEnumName = ytEnumName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSelected() {
|
public boolean isLibraryOrYouTab() {
|
||||||
ImageView view = imageViewRef.get();
|
return this == LIBRARY_YOU || this == LIBRARY_PIVOT_UNKNOWN
|
||||||
return view != null && view.isSelected();
|
|| this == LIBRARY_OLD_UI || this == LIBRARY_INCOGNITO
|
||||||
|
|| this == LIBRARY_LOGGED_OUT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,13 +159,13 @@ public class SBRequester {
|
|||||||
messageToToast = str("revanced_sb_submit_failed_duplicate");
|
messageToToast = str("revanced_sb_submit_failed_duplicate");
|
||||||
break;
|
break;
|
||||||
case 403:
|
case 403:
|
||||||
messageToToast = str("revanced_sb_submit_failed_forbidden", Requester.parseErrorJsonAndDisconnect(connection));
|
messageToToast = str("revanced_sb_submit_failed_forbidden", Requester.parseErrorStringAndDisconnect(connection));
|
||||||
break;
|
break;
|
||||||
case 429:
|
case 429:
|
||||||
messageToToast = str("revanced_sb_submit_failed_rate_limit");
|
messageToToast = str("revanced_sb_submit_failed_rate_limit");
|
||||||
break;
|
break;
|
||||||
case 400:
|
case 400:
|
||||||
messageToToast = str("revanced_sb_submit_failed_invalid", Requester.parseErrorJsonAndDisconnect(connection));
|
messageToToast = str("revanced_sb_submit_failed_invalid", Requester.parseErrorStringAndDisconnect(connection));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
messageToToast = str("revanced_sb_submit_failed_unknown_error", responseCode, connection.getResponseMessage());
|
messageToToast = str("revanced_sb_submit_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||||
@ -223,7 +223,7 @@ public class SBRequester {
|
|||||||
break;
|
break;
|
||||||
case 403:
|
case 403:
|
||||||
Utils.showToastLong(
|
Utils.showToastLong(
|
||||||
str("revanced_sb_vote_failed_forbidden", Requester.parseErrorJsonAndDisconnect(connection)));
|
str("revanced_sb_vote_failed_forbidden", Requester.parseErrorStringAndDisconnect(connection)));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Utils.showToastLong(
|
Utils.showToastLong(
|
||||||
|
@ -104,5 +104,17 @@ class SwipeControlsConfigurationProvider(
|
|||||||
val shouldSaveAndRestoreBrightness: Boolean
|
val shouldSaveAndRestoreBrightness: Boolean
|
||||||
get() = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get()
|
get() = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* should auto-brightness be enabled at the lowest value of the brightness gesture
|
||||||
|
*/
|
||||||
|
val shouldLowestValueEnableAutoBrightness: Boolean
|
||||||
|
get() = Settings.SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS.get()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* variable that stores the brightness gesture value in the settings
|
||||||
|
*/
|
||||||
|
var savedScreenBrightnessValue: Float
|
||||||
|
get() = Settings.SWIPE_BRIGHTNESS_VALUE.get()
|
||||||
|
set(value) = Settings.SWIPE_BRIGHTNESS_VALUE.save(value)
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -166,20 +166,31 @@ class SwipeControlsHostActivity : Activity() {
|
|||||||
contentRoot.addView(overlay)
|
contentRoot.addView(overlay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flag that indicates whether the brightness has been saved and restored default brightness
|
||||||
|
private var isBrightnessSaved = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* called when the player type changes
|
* called when the player type changes
|
||||||
*
|
*
|
||||||
* @param type the new player type
|
* @param type the new player type
|
||||||
*/
|
*/
|
||||||
private fun onPlayerTypeChanged(type: PlayerType) {
|
private fun onPlayerTypeChanged(type: PlayerType) {
|
||||||
if (config.shouldSaveAndRestoreBrightness) {
|
when {
|
||||||
when (type) {
|
// If saving and restoring brightness is enabled, and the player type is WATCH_WHILE_FULLSCREEN,
|
||||||
PlayerType.WATCH_WHILE_FULLSCREEN -> screen?.restore()
|
// and brightness has already been saved, then restore the screen brightness
|
||||||
else -> {
|
config.shouldSaveAndRestoreBrightness && type == PlayerType.WATCH_WHILE_FULLSCREEN && isBrightnessSaved -> {
|
||||||
|
screen?.restore()
|
||||||
|
isBrightnessSaved = false
|
||||||
|
}
|
||||||
|
// If saving and restoring brightness is enabled, and brightness has not been saved,
|
||||||
|
// then save the current screen state, restore default brightness, and mark brightness as saved
|
||||||
|
config.shouldSaveAndRestoreBrightness && !isBrightnessSaved -> {
|
||||||
screen?.save()
|
screen?.save()
|
||||||
screen?.restoreDefaultBrightness()
|
screen?.restoreDefaultBrightness()
|
||||||
|
isBrightnessSaved = true
|
||||||
}
|
}
|
||||||
}
|
// If saving and restoring brightness is disabled, simply keep the default brightness
|
||||||
|
else -> screen?.restoreDefaultBrightness()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
package app.revanced.integrations.youtube.swipecontrols.controller
|
package app.revanced.integrations.youtube.swipecontrols.controller
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity
|
||||||
import app.revanced.integrations.youtube.swipecontrols.misc.clamp
|
import app.revanced.integrations.youtube.swipecontrols.misc.clamp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* controller to adjust the screen brightness level
|
* controller to adjust the screen brightness level
|
||||||
*
|
*
|
||||||
* @param host the host activity of which the brightness is adjusted
|
* @param host the host activity of which the brightness is adjusted, the main controller instance
|
||||||
*/
|
*/
|
||||||
class ScreenBrightnessController(
|
class ScreenBrightnessController(
|
||||||
private val host: Activity,
|
val host: SwipeControlsHostActivity,
|
||||||
) {
|
) {
|
||||||
/**
|
|
||||||
* screen brightness saved by [save]
|
|
||||||
*/
|
|
||||||
private var savedScreenBrightness: Float? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the current screen brightness in percent, ranging from 0.0 to 100.0
|
* the current screen brightness in percent, ranging from 0.0 to 100.0
|
||||||
@ -26,13 +22,6 @@ class ScreenBrightnessController(
|
|||||||
rawScreenBrightness = (value.toFloat() / 100f).clamp(0f, 1f)
|
rawScreenBrightness = (value.toFloat() / 100f).clamp(0f, 1f)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* restore the screen brightness to the default device brightness
|
|
||||||
*/
|
|
||||||
fun restoreDefaultBrightness() {
|
|
||||||
rawScreenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is the screen brightness set to device- default?
|
* is the screen brightness set to device- default?
|
||||||
*/
|
*/
|
||||||
@ -40,22 +29,35 @@ class ScreenBrightnessController(
|
|||||||
get() = (rawScreenBrightness == WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE)
|
get() = (rawScreenBrightness == WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save the current screen brightness, to be brought back using [restore]
|
* restore the screen brightness to the default device brightness
|
||||||
|
*/
|
||||||
|
fun restoreDefaultBrightness() {
|
||||||
|
rawScreenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag that indicates whether the brightness has been restored
|
||||||
|
private var isBrightnessRestored = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save the current screen brightness into settings, to be brought back using [restore]
|
||||||
*/
|
*/
|
||||||
fun save() {
|
fun save() {
|
||||||
if (savedScreenBrightness == null) {
|
if (isBrightnessRestored) {
|
||||||
savedScreenBrightness = rawScreenBrightness
|
// Saves the current screen brightness value into settings
|
||||||
|
host.config.savedScreenBrightnessValue = rawScreenBrightness
|
||||||
|
// Reset the flag
|
||||||
|
isBrightnessRestored = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* restore the screen brightness saved using [save]
|
* restore the screen brightness from settings saved using [save]
|
||||||
*/
|
*/
|
||||||
fun restore() {
|
fun restore() {
|
||||||
savedScreenBrightness?.let {
|
// Restores the screen brightness value from the saved settings
|
||||||
rawScreenBrightness = it
|
rawScreenBrightness = host.config.savedScreenBrightnessValue
|
||||||
}
|
// Mark that brightness has been restored
|
||||||
savedScreenBrightness = null
|
isBrightnessRestored = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,12 +77,17 @@ class VolumeAndBrightnessScrollerImpl(
|
|||||||
),
|
),
|
||||||
) { _, _, direction ->
|
) { _, _, direction ->
|
||||||
screenController?.run {
|
screenController?.run {
|
||||||
if (screenBrightness > 0 || direction > 0) {
|
val shouldAdjustBrightness = if (host.config.shouldLowestValueEnableAutoBrightness) {
|
||||||
|
screenBrightness > 0 || direction > 0
|
||||||
|
} else {
|
||||||
|
screenBrightness >= 0 || direction >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldAdjustBrightness) {
|
||||||
screenBrightness += direction
|
screenBrightness += direction
|
||||||
} else {
|
} else {
|
||||||
restoreDefaultBrightness()
|
restoreDefaultBrightness()
|
||||||
}
|
}
|
||||||
|
|
||||||
overlayController.onBrightnessChanged(screenBrightness)
|
overlayController.onBrightnessChanged(screenBrightness)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import app.revanced.integrations.shared.StringRef.str
|
||||||
import app.revanced.integrations.shared.Utils
|
import app.revanced.integrations.shared.Utils
|
||||||
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsConfigurationProvider
|
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsConfigurationProvider
|
||||||
import app.revanced.integrations.youtube.swipecontrols.misc.SwipeControlsOverlay
|
import app.revanced.integrations.youtube.swipecontrols.misc.SwipeControlsOverlay
|
||||||
@ -122,10 +123,13 @@ class SwipeControlsOverlayLayout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBrightnessChanged(brightness: Double) {
|
override fun onBrightnessChanged(brightness: Double) {
|
||||||
if (brightness > 0) {
|
if (config.shouldLowestValueEnableAutoBrightness && brightness <= 0) {
|
||||||
|
showFeedbackView(
|
||||||
|
str("revanced_swipe_lowest_value_enable_auto_brightness_overlay_text"),
|
||||||
|
autoBrightnessIcon,
|
||||||
|
)
|
||||||
|
} else if (brightness >= 0) {
|
||||||
showFeedbackView("${round(brightness).toInt()}%", manualBrightnessIcon)
|
showFeedbackView("${round(brightness).toInt()}%", manualBrightnessIcon)
|
||||||
} else {
|
|
||||||
showFeedbackView("AUTO", autoBrightnessIcon)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
version = 1.7.0
|
version = 1.8.0-dev.20
|
||||||
|
Loading…
x
Reference in New Issue
Block a user