fix(youtube/return-youtube-dislike): render dislikes when scrolling into the screen (#350)

Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
LisoUseInAIKyrios 2023-04-19 10:36:10 +04:00 committed by GitHub
parent 48050c1c50
commit 41c07f77f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 212 additions and 209 deletions

View File

@ -1,48 +1,108 @@
package app.revanced.integrations.patches;
import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote;
import android.text.SpannableString;
import android.text.Spanned;
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
import androidx.annotation.NonNull;
import java.util.concurrent.atomic.AtomicReference;
/**
* TODO: delete this empty class, and point the patch to {@link ReturnYouTubeDislike}
*/
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
public class ReturnYouTubeDislikePatch {
/**
* Injection point
* Injection point.
*/
public static void newVideoLoaded(String videoId) {
ReturnYouTubeDislike.newVideoLoaded(videoId);
try {
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
ReturnYouTubeDislike.newVideoLoaded(videoId);
} catch (Exception ex) {
LogHelper.printException(() -> "newVideoLoaded failure", ex);
}
}
/**
* Injection point
* Injection point.
*
* Called when a litho text component is created
* Called when a litho text component is initially created,
* and also when a Span is later reused again (such as scrolling off/on screen).
*
* This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
* This method can be called multiple times for the same UI element (including after dislikes was added).
*
* @param textRef Cache reference to the like/dislike char sequence,
* which may or may not be the same as the original span parameter.
* If dislikes are added, the atomic reference must be set to the replacement span.
* @param original Original span that was created or reused by Litho.
* @return The original span (if nothing should change), or a replacement span that contains dislikes.
*/
public static void onComponentCreated(Object conversionContext, AtomicReference<Object> textRef) {
ReturnYouTubeDislike.onComponentCreated(conversionContext, textRef);
@NonNull
public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
@NonNull AtomicReference<CharSequence> textRef,
@NonNull CharSequence original) {
try {
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
return original;
}
SpannableString replacement = ReturnYouTubeDislike.getDislikeSpanForContext(conversionContext, original);
if (replacement != null) {
textRef.set(replacement);
return replacement;
}
} catch (Exception ex) {
LogHelper.printException(() -> "onComponentCreated AtomicReference failure", ex);
}
return original;
}
/**
* Injection point
* Injection point.
*
* Called when a Shorts dislike Spannable is created
* Called when a Shorts dislike Spanned is created.
*/
public static Spanned onShortsComponentCreated(Spanned dislike) {
return ReturnYouTubeDislike.onShortsComponentCreated(dislike);
public static Spanned onShortsComponentCreated(Spanned original) {
try {
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
return original;
}
SpannableString replacement = ReturnYouTubeDislike.getDislikeSpanForShort(original);
if (replacement != null) {
return replacement;
}
} catch (Exception ex) {
LogHelper.printException(() -> "onShortsComponentCreated failure", ex);
}
return original;
}
/**
* Injection point
* Injection point.
*
* Called when the like/dislike button is clicked
* Called when the user likes or dislikes.
*
* @param vote -1 (dislike), 0 (none) or 1 (like)
* @param vote int that matches {@link ReturnYouTubeDislike.Vote#value}
*/
public static void sendVote(int vote) {
ReturnYouTubeDislike.sendVote(vote);
try {
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
return;
}
for (Vote v : Vote.values()) {
if (v.value == vote) {
ReturnYouTubeDislike.sendVote(v);
return;
}
}
LogHelper.printException(() -> "Unknown vote type: " + vote);
} catch (Exception ex) {
LogHelper.printException(() -> "sendVote failure", ex);
}
}
}

View File

@ -31,7 +31,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import app.revanced.integrations.returnyoutubedislike.requests.RYDVoteData;
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
@ -41,10 +40,13 @@ import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.ReVancedUtils;
import app.revanced.integrations.utils.ThemeHelper;
/**
* Because Litho creates spans using multiple threads, this entire class supports multithreading as well.
*/
public class ReturnYouTubeDislike {
/**
* Maximum amount of time to block the UI from updates while waiting for network call to complete.
* <p>
*
* Must be less than 5 seconds, as per:
* https://developer.android.com/topic/performance/vitals/anr
*/
@ -52,18 +54,17 @@ public class ReturnYouTubeDislike {
/**
* Unique placeholder character, used to detect if a segmented span already has dislikes added to it.
* Can be any almost any non-visible character
* Can be any almost any non-visible character.
*/
private static final char MIDDLE_SEPARATOR_CHARACTER = '\u2009'; // 'narrow space' character
/**
* Used to send votes, one by one, in the same order the user created them
* Used to send votes, one by one, in the same order the user created them.
*/
private static final ExecutorService voteSerialExecutor = Executors.newSingleThreadExecutor();
/**
* Used to guard {@link #currentVideoId} and {@link #voteFetchFuture},
* as multiple threads access this class.
* Used to guard {@link #currentVideoId} and {@link #voteFetchFuture}.
*/
private static final Object videoIdLockObject = new Object();
@ -71,14 +72,13 @@ public class ReturnYouTubeDislike {
@GuardedBy("videoIdLockObject")
private static String currentVideoId;
/**
* If {@link #currentVideoId} and the RYD data is for the last shorts loaded
* If {@link #currentVideoId} and the RYD data is for the last shorts loaded.
*/
private static volatile boolean lastVideoLoadedWasShort;
/**
* Stores the results of the vote api fetch, and used as a barrier to wait until fetch completes
* Stores the results of the vote api fetch, and used as a barrier to wait until fetch completes.
*/
@Nullable
@GuardedBy("videoIdLockObject")
@ -86,19 +86,31 @@ public class ReturnYouTubeDislike {
/**
* Original dislike span, before modifications.
* Required for segmented layout
*/
@Nullable
@GuardedBy("videoIdLockObject")
private static Spanned originalDislikeSpan;
/**
* Replacement like/dislike span that includes formatted dislikes and is ready to display
* Replacement like/dislike span that includes formatted dislikes.
* Used to prevent recreating the same span multiple times.
*/
@Nullable
@GuardedBy("videoIdLockObject")
private static SpannableString replacementLikeDislikeSpan;
/**
* For formatting dislikes as number.
*/
@GuardedBy("ReturnYouTubeDislike.class") // not thread safe
private static CompactDecimalFormat dislikeCountFormatter;
/**
* For formatting dislikes as percentage.
*/
@GuardedBy("ReturnYouTubeDislike.class")
private static NumberFormat dislikePercentageFormatter;
public enum Vote {
LIKE(1),
DISLIKE(-1),
@ -114,18 +126,6 @@ public class ReturnYouTubeDislike {
private ReturnYouTubeDislike() {
} // only static methods
/**
* Used to format like/dislike count.
*/
@GuardedBy("ReturnYouTubeDislike.class") // not thread safe
private static CompactDecimalFormat dislikeCountFormatter;
/**
* Used to format like/dislike count.
*/
@GuardedBy("ReturnYouTubeDislike.class")
private static NumberFormat dislikePercentageFormatter;
public static void onEnabledChange(boolean enabled) {
if (!enabled) {
// Must clear old values, to protect against using stale data
@ -148,7 +148,7 @@ public class ReturnYouTubeDislike {
}
/**
* Should be called if user changes settings for dislikes appearance.
* Should be called after a user dislikes, or if the user changes settings for dislikes appearance.
*/
public static void clearCache() {
synchronized (videoIdLockObject) {
@ -174,146 +174,113 @@ public class ReturnYouTubeDislike {
}
public static void newVideoLoaded(@NonNull String videoId) {
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
Objects.requireNonNull(videoId);
try {
Objects.requireNonNull(videoId);
PlayerType currentPlayerType = PlayerType.getCurrent();
if (currentPlayerType == PlayerType.INLINE_MINIMAL) {
LogHelper.printDebug(() -> "Ignoring inline playback of video: " + videoId);
PlayerType currentPlayerType = PlayerType.getCurrent();
if (currentPlayerType == PlayerType.INLINE_MINIMAL) {
LogHelper.printDebug(() -> "Ignoring inline playback of video: " + videoId);
setCurrentVideoId(null);
return;
}
synchronized (videoIdLockObject) {
if (videoId.equals(currentVideoId)) {
return; // already loaded
}
if (!ReVancedUtils.isNetworkConnected()) { // must do network check after verifying it's a new video id
LogHelper.printDebug(() -> "Network not connected, ignoring video: " + videoId);
setCurrentVideoId(null);
return;
}
synchronized (videoIdLockObject) {
if (videoId.equals(currentVideoId)) {
return; // already loaded
}
if (!ReVancedUtils.isNetworkConnected()) { // must do network check after verifying it's a new video id
LogHelper.printDebug(() -> "Network not connected, ignoring video: " + videoId);
setCurrentVideoId(null);
return;
}
LogHelper.printDebug(() -> "New video loaded: " + videoId + " playerType: " + currentPlayerType);
setCurrentVideoId(videoId);
LogHelper.printDebug(() -> "New video loaded: " + videoId + " playerType: " + currentPlayerType);
setCurrentVideoId(videoId);
// If a Short is opened while a regular video is on screen, this will incorrectly set this as false.
// But this check is needed to fix unusual situations of opening/closing the app
// while both a regular video and a short are on screen.
lastVideoLoadedWasShort = PlayerType.getCurrent().isNoneOrHidden();
// If a Short is opened while a regular video is on screen, this will incorrectly set this as false.
// But this check is needed to fix unusual situations of opening/closing the app
// while both a regular video and a short are on screen.
lastVideoLoadedWasShort = PlayerType.getCurrent().isNoneOrHidden();
// no need to wrap the call in a try/catch,
// as any exceptions are propagated out in the later Future#Get call
voteFetchFuture = ReVancedUtils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId));
}
} catch (Exception ex) {
LogHelper.printException(() -> "Failed to load new video: " + videoId, ex);
// No need to wrap the call in a try/catch,
// as any exceptions are propagated out in the later Future#Get call.
voteFetchFuture = ReVancedUtils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId));
}
}
/**
* Called when a litho text component is created.
*
* This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
* This method can be called multiple times for the same UI element (including after dislikes was added)
*
* @param textRef atomic reference should always be non null, but the spanned reference inside can be null.
* @return NULL if the span does not need changing or if RYD is not available.
*/
public static void onComponentCreated(@NonNull Object conversionContext, @NonNull AtomicReference<Object> textRef) {
try {
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
if (PlayerType.getCurrent().isNoneOrHidden()) {
return;
}
String conversionContextString = conversionContext.toString();
final boolean isSegmentedButton;
if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) {
isSegmentedButton = true;
} else if (conversionContextString.contains("|dislike_button.eml|")) {
isSegmentedButton = false;
} else {
return;
}
if (lastVideoLoadedWasShort) {
// user:
// 1, opened a video
// 2. opened a short (without closing the regular video)
// 3. closed the short
// 4. regular video is now present, but the videoId and RYD data is still for the short
LogHelper.printDebug(() -> "Ignoring onComponentCreated(), as data loaded is is for prior short");
return;
}
Spanned replacement = waitForFetchAndUpdateReplacementSpan((Spanned) textRef.get(), isSegmentedButton);
if (replacement != null) {
textRef.set(replacement);
}
} catch (Exception ex) {
LogHelper.printException(() -> "onComponentCreated failure", ex);
@Nullable
public static SpannableString getDislikeSpanForContext(@NonNull Object conversionContext, @NonNull CharSequence original) {
if (PlayerType.getCurrent().isNoneOrHidden()) {
return null;
}
String conversionContextString = conversionContext.toString();
final boolean isSegmentedButton;
if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) {
isSegmentedButton = true;
} else if (conversionContextString.contains("|dislike_button.eml|")) {
isSegmentedButton = false;
} else {
return null;
}
if (lastVideoLoadedWasShort) {
// user:
// 1, opened a video
// 2. opened a short (without closing the regular video)
// 3. closed the short
// 4. regular video is now present, but the videoId and RYD data is still for the short
LogHelper.printDebug(() -> "Ignoring getDislikeSpanForContext(), as data loaded is for prior short");
return null;
}
return waitForFetchAndUpdateReplacementSpan((Spannable) original, isSegmentedButton);
}
/**
* Called when a Shorts dislike Spannable is created.
*/
public static Spanned onShortsComponentCreated(Spanned original) {
try {
if (SettingsEnum.RYD_ENABLED.getBoolean()) {
lastVideoLoadedWasShort = true; // it's now certain the video and data are a short
Spanned replacement = waitForFetchAndUpdateReplacementSpan(original, false);
if (replacement != null) {
return replacement;
}
}
} catch (Exception ex) {
LogHelper.printException(() -> "onShortsComponentCreated failure", ex);
}
return original;
public static SpannableString getDislikeSpanForShort(@NonNull Spanned original) {
lastVideoLoadedWasShort = true; // it's now certain the video and data are a short
return waitForFetchAndUpdateReplacementSpan(original, false);
}
// alternatively, this could check if the span contains one of the custom created spans, but this is simple and quick
// Alternatively, this could check if the span contains one of the custom created spans, but this is simple and quick.
private static boolean isPreviouslyCreatedSegmentedSpan(@NonNull Spanned span) {
return span.toString().indexOf(MIDDLE_SEPARATOR_CHARACTER) != -1;
}
/**
* @return NULL if the span does not need changing or if RYD is not available
* @return NULL if the span does not need changing or if RYD is not available.
*/
@Nullable
private static SpannableString waitForFetchAndUpdateReplacementSpan(@Nullable Spanned oldSpannable, boolean isSegmentedButton) {
if (oldSpannable == null) {
LogHelper.printDebug(() -> "Cannot add dislikes (injection code was called with null Span)");
return null;
}
private static SpannableString waitForFetchAndUpdateReplacementSpan(@NonNull Spanned oldSpannable, boolean isSegmentedButton) {
try {
synchronized (videoIdLockObject) {
if (oldSpannable.equals(replacementLikeDislikeSpan)) {
LogHelper.printDebug(() -> "Ignoring span that already contains dislikes");
return null;
}
if (replacementLikeDislikeSpan != null) {
LogHelper.printDebug(() -> "Using previously created dislike span");
return replacementLikeDislikeSpan;
}
if (isSegmentedButton) {
if (isPreviouslyCreatedSegmentedSpan(oldSpannable)) {
// need to recreate using original, as oldSpannable has prior outdated dislike values
oldSpannable = originalDislikeSpan;
if (oldSpannable == null) {
LogHelper.printDebug(() -> "Cannot add dislikes - original span is null"); // should never happen
return null;
}
} else {
originalDislikeSpan = oldSpannable; // most up to date original
String oldSpannableString = oldSpannable.toString();
if (replacementLikeDislikeSpan.toString().equals(oldSpannableString)) {
LogHelper.printDebug(() -> "Ignoring previously created dislikes span");
return null;
}
if (originalDislikeSpan.toString().equals(oldSpannableString)) {
LogHelper.printDebug(() -> "Replacing span with previously created dislike span");
return replacementLikeDislikeSpan;
}
}
if (isSegmentedButton && isPreviouslyCreatedSegmentedSpan(oldSpannable)) {
// need to recreate using original, as oldSpannable has prior outdated dislike values
oldSpannable = originalDislikeSpan;
if (oldSpannable == null) {
LogHelper.printDebug(() -> "Cannot add dislikes - original span is null"); // should never happen
return null;
}
} else {
originalDislikeSpan = oldSpannable; // most up to date original
}
}
// Must block the current thread until fetching is done
// There's no known way to edit the text after creation yet
// Must block the current thread until fetching is done.
// There's no known way to edit the text after creation yet.
Future<RYDVoteData> fetchFuture = getVoteFetchFuture();
if (fetchFuture == null) {
LogHelper.printDebug(() -> "fetch future not available (user enabled RYD while video was playing?)");
@ -340,37 +307,16 @@ public class ReturnYouTubeDislike {
return null;
}
/**
* Called when the like/dislike button is clicked.
*
* @param vote int that matches {@link Vote#value}
*/
public static void sendVote(int vote) {
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
try {
for (Vote v : Vote.values()) {
if (v.value == vote) {
sendVote(v);
return;
}
}
LogHelper.printException(() -> "Unknown vote type: " + vote);
} catch (Exception ex) {
LogHelper.printException(() -> "sendVote failure", ex);
}
}
private static void sendVote(@NonNull Vote vote) {
public static void sendVote(@NonNull Vote vote) {
ReVancedUtils.verifyOnMainThread();
Objects.requireNonNull(vote);
try {
// Must make a local copy of videoId, since it may change between now and when the vote thread runs
// Must make a local copy of videoId, since it may change between now and when the vote thread runs.
String videoIdToVoteFor = getCurrentVideoId();
if (videoIdToVoteFor == null || lastVideoLoadedWasShort != PlayerType.getCurrent().isNoneOrHidden()) {
// User enabled RYD after starting playback of a video.
// Or shorts was loaded with regular video present, then shorts was closed,
// and then user voted on the now visible original video
// and then user voted on the now visible original video.
// Cannot send a vote, because the loaded videoId is for the wrong video.
ReVancedUtils.showToastLong(str("revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted"));
return;
@ -387,15 +333,15 @@ public class ReturnYouTubeDislike {
}
});
clearCache(); // ui values need updating
clearCache(); // UI needs updating
// update the downloaded vote data
// Update the downloaded vote data.
Future<RYDVoteData> future = getVoteFetchFuture();
if (future == null) {
LogHelper.printException(() -> "Cannot update UI dislike count - vote fetch is null");
return;
}
// the future should always be completed before user can like/dislike, but use a timeout just in case
// The future should always be completed before user can like/dislike, but use a timeout just in case.
RYDVoteData voteData = future.get(MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE, TimeUnit.MILLISECONDS);
if (voteData == null) {
// RYD fetch failed
@ -409,10 +355,10 @@ public class ReturnYouTubeDislike {
}
/**
* Must call off main thread, as this will make a network call if user is not yet registered
* Must call off main thread, as this will make a network call if user is not yet registered.
*
* @return ReturnYouTubeDislike user ID. If user registration has never happened
* and the network call fails, this returns NULL
* and the network call fails, this returns NULL.
*/
@Nullable
private static String getUserId() {
@ -423,7 +369,7 @@ public class ReturnYouTubeDislike {
return userId;
}
userId = ReturnYouTubeDislikeApi.registerAsNewUser(); // blocks until network call is completed
userId = ReturnYouTubeDislikeApi.registerAsNewUser();
if (userId != null) {
SettingsEnum.RYD_USER_ID.saveValue(userId);
}
@ -431,25 +377,25 @@ public class ReturnYouTubeDislike {
}
/**
* @param isSegmentedButton if UI is using the segmented single UI component for both like and dislike
* @param isSegmentedButton If UI is using the segmented single UI component for both like and dislike.
*/
private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, boolean isSegmentedButton, @NonNull RYDVoteData voteData) {
if (!isSegmentedButton) {
// simple replacement of 'dislike' with a number/percentage
// Simple replacement of 'dislike' with a number/percentage.
return newSpannableWithDislikes(oldSpannable, voteData);
}
// note: some locales use right to left layout (arabic, hebrew, etc),
// and care must be taken to retain the existing RTL encoding character on the likes string
// otherwise text will incorrectly show as left to right
// if making changes to this code, change device settings to a RTL language and verify layout is correct
// Note: Some locales use right to left layout (arabic, hebrew, etc),
// and care must be taken to retain the existing RTL encoding character on the likes string,
// otherwise text will incorrectly show as left to right.
// If making changes to this code, change device settings to a RTL language and verify layout is correct.
String oldLikesString = oldSpannable.toString();
// YouTube creators can hide the like count on a video,
// and the like count appears as a device language specific string that says 'Like'
// check if the string contains any numbers
// and the like count appears as a device language specific string that says 'Like'.
// Check if the string contains any numbers.
if (!stringContainsNumber(oldLikesString)) {
// likes are hidden.
// Likes are hidden.
// RYD does not provide usable data for these types of videos,
// and the API returns bogus data (zero likes and zero dislikes)
// discussion about this: https://github.com/Anarios/return-youtube-dislike/discussions/530
@ -457,7 +403,7 @@ public class ReturnYouTubeDislike {
// example video: https://www.youtube.com/watch?v=UnrU5vxCHxw
// RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw
//
// Change the "Likes" string to show that likes and dislikes are hidden
// Change the "Likes" string to show that likes and dislikes are hidden.
String hiddenMessageString = str("revanced_ryd_video_likes_hidden_by_video_owner");
return newSpanUsingStylingOfAnotherSpan(oldSpannable, hiddenMessageString);
}
@ -467,7 +413,7 @@ public class ReturnYouTubeDislike {
final int separatorColor = ThemeHelper.isDarkTheme()
? 0x29AAAAAA // transparent dark gray
: 0xFFD9D9D9; // light gray
DisplayMetrics dp = ReVancedUtils.getContext().getResources().getDisplayMetrics();
DisplayMetrics dp = Objects.requireNonNull(ReVancedUtils.getContext()).getResources().getDisplayMetrics();
if (!compactLayout) {
// left separator
@ -511,9 +457,9 @@ public class ReturnYouTubeDislike {
}
/**
* Correctly handles any unicode numbers (such as Arabic numbers)
* Correctly handles any unicode numbers (such as Arabic numbers).
*
* @return if the string contains at least 1 number
* @return if the string contains at least 1 number.
*/
private static boolean stringContainsNumber(@NonNull String text) {
for (int index = 0, length = text.length(); index < length; index++) {
@ -545,10 +491,10 @@ public class ReturnYouTubeDislike {
synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize
if (dislikeCountFormatter == null) {
// Note: Java number formatters will use the locale specific number characters.
// such as Arabic which formats "1.2" into "١٫٢"
// such as Arabic which formats "1.234" into "۱,۲۳٤"
// But YouTube disregards locale specific number characters
// and instead shows english number characters everywhere.
Locale locale = ReVancedUtils.getContext().getResources().getConfiguration().locale;
Locale locale = Objects.requireNonNull(ReVancedUtils.getContext()).getResources().getConfiguration().locale;
LogHelper.printDebug(() -> "Locale: " + locale);
dislikeCountFormatter = CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT);
}
@ -563,7 +509,7 @@ public class ReturnYouTubeDislike {
private static String formatDislikePercentage(float dislikePercentage) {
synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize
if (dislikePercentageFormatter == null) {
Locale locale = ReVancedUtils.getContext().getResources().getConfiguration().locale;
Locale locale = Objects.requireNonNull(ReVancedUtils.getContext()).getResources().getConfiguration().locale;
LogHelper.printDebug(() -> "Locale: " + locale);
dislikePercentageFormatter = NumberFormat.getPercentInstance(locale);
}

View File

@ -203,7 +203,7 @@ public class ReturnYouTubeDislikeApi {
@SuppressWarnings("NonAtomicOperationOnVolatileField") // do not want to pay performance cost of full synchronization for debug fields that are only estimates anyways
private static void updateStatistics(long timeNetworkCallStarted, long timeNetworkCallEnded, boolean connectionError, boolean rateLimitHit) {
if (connectionError && rateLimitHit) {
throw new IllegalArgumentException("both connection error and rate limit parameter were true");
throw new IllegalArgumentException();
}
final long responseTimeOfFetchCall = timeNetworkCallEnded - timeNetworkCallStarted;
fetchCallResponseTimeTotal += responseTimeOfFetchCall;
@ -320,7 +320,7 @@ public class ReturnYouTubeDislikeApi {
return confirmRegistration(userId, solution);
}
LogHelper.printException(() -> "Failed to register new user: " + userId
+ " response code was: " + responseCode);
+ " response code was: " + responseCode); // failed attempt, and ok to log userId
connection.disconnect();
} catch (Exception ex) {
LogHelper.printException(() -> "Failed to register user", ex);
@ -337,7 +337,7 @@ public class ReturnYouTubeDislikeApi {
if (checkIfRateLimitInEffect("confirmRegistration")) {
return null;
}
LogHelper.printDebug(() -> "Trying to confirm registration for user: " + userId + " with solution: " + solution);
LogHelper.printDebug(() -> "Trying to confirm registration with solution: " + solution);
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_REGISTRATION, userId);
applyCommonPostRequestSettings(connection);
@ -355,7 +355,7 @@ public class ReturnYouTubeDislikeApi {
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
String result = Requester.parseJson(connection);
if (result.equalsIgnoreCase("true")) {
LogHelper.printDebug(() -> "Registration confirmation successful for user: " + userId);
LogHelper.printDebug(() -> "Registration confirmation successful");
return userId;
}
LogHelper.printException(() -> "Failed to confirm registration for user: " + userId
@ -382,8 +382,7 @@ public class ReturnYouTubeDislikeApi {
if (checkIfRateLimitInEffect("sendVote")) {
return false;
}
LogHelper.printDebug(() -> "Trying to vote for video: "
+ videoId + " with vote: " + vote + " user: " + userId);
LogHelper.printDebug(() -> "Trying to vote for video: " + videoId + " with vote: " + vote);
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.SEND_VOTE);
applyCommonPostRequestSettings(connection);
@ -408,11 +407,10 @@ public class ReturnYouTubeDislikeApi {
return confirmVote(videoId, userId, solution);
}
LogHelper.printException(() -> "Failed to send vote for video: " + videoId
+ " userId: " + userId + " vote: " + vote + " response code was: " + responseCode);
+ " vote: " + vote + " response code was: " + responseCode);
connection.disconnect(); // something went wrong, might as well disconnect
} catch (Exception ex) {
LogHelper.printException(() -> "Failed to send vote for video: " + videoId
+ " user: " + userId + " vote: " + vote, ex);
LogHelper.printException(() -> "Failed to send vote for video: " + videoId + " vote: " + vote, ex);
}
return false;
}
@ -427,8 +425,7 @@ public class ReturnYouTubeDislikeApi {
if (checkIfRateLimitInEffect("confirmVote")) {
return false;
}
LogHelper.printDebug(() -> "Trying to confirm vote for video: "
+ videoId + " user: " + userId + " solution: " + solution);
LogHelper.printDebug(() -> "Trying to confirm vote for video: " + videoId + " solution: " + solution);
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.CONFIRM_VOTE);
applyCommonPostRequestSettings(connection);
@ -450,15 +447,15 @@ public class ReturnYouTubeDislikeApi {
return true;
}
LogHelper.printException(() -> "Failed to confirm vote for video: " + videoId
+ " user: " + userId + " solution: " + solution + " response string was: " + result);
+ " solution: " + solution + " response string was: " + result);
} else {
LogHelper.printException(() -> "Failed to confirm vote for video: " + videoId
+ " user: " + userId + " solution: " + solution + " response code was: " + responseCode);
+ " solution: " + solution + " response code was: " + responseCode);
}
connection.disconnect(); // something went wrong, might as well disconnect
} catch (Exception ex) {
LogHelper.printException(() -> "Failed to confirm vote for video: " + videoId
+ " user: " + userId + " solution: " + solution, ex);
+ " solution: " + solution, ex);
}
return false;
}