add support for voting

This commit is contained in:
caneleex 2021-04-22 18:43:14 +02:00
parent df13138e25
commit 5136350702
6 changed files with 331 additions and 15 deletions

View File

@ -18,7 +18,6 @@ import android.preference.SwitchPreference;
import android.text.InputType;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import static pl.jakubweg.SponsorBlockSettings.DefaultBehaviour;
@ -28,10 +27,10 @@ import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_NEW_SEGMENT_ENABL
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_UUID;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_VOTING_ENABLED;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_NAME;
import static pl.jakubweg.SponsorBlockSettings.adjustNewSegmentMillis;
import static pl.jakubweg.SponsorBlockSettings.countSkips;
import static pl.jakubweg.SponsorBlockSettings.getPreferences;
import static pl.jakubweg.SponsorBlockSettings.setSeenGuidelines;
import static pl.jakubweg.SponsorBlockSettings.showToastWhenSkippedAutomatically;
import static pl.jakubweg.SponsorBlockSettings.uuid;
@ -104,6 +103,17 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
});
}
{
SwitchPreference preference = new SwitchPreference(context);
preferenceScreen.addPreference(preference);
preference.setTitle(str("enable_voting"));
preference.setSummary(str("enable_voting_sum"));
preference.setKey(PREFERENCES_KEY_VOTING_ENABLED);
preference.setDefaultValue(SponsorBlockSettings.isVotingEnabled);
preference.setChecked(SponsorBlockSettings.isVotingEnabled);
preferencesToDisableWhenSBDisabled.add(preference);
}
addGeneralCategory(context, preferenceScreen);
addSegmentsCategory(context, preferenceScreen);
addAboutCategory(context, preferenceScreen);

View File

@ -7,7 +7,6 @@ import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@ -25,8 +24,10 @@ public class SponsorBlockSettings {
public static final String PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED = "sb-enabled";
public static final String PREFERENCES_KEY_SEEN_GUIDELINES = "sb-seen-gl";
public static final String PREFERENCES_KEY_NEW_SEGMENT_ENABLED = "sb-new-segment-enabled";
public static final String PREFERENCES_KEY_VOTING_ENABLED = "sb-voting-enabled";
public static final String sponsorBlockSkipSegmentsUrl = "https://sponsor.ajay.app/api/skipSegments";
public static final String sponsorBlockViewedUrl = "https://sponsor.ajay.app/api/viewedVideoSponsorTime";
public static final String sponsorBlockVoteUrl = "https://sponsor.ajay.app/api/voteOnSponsorTime";
public static final SegmentBehaviour DefaultBehaviour = SegmentBehaviour.SkipAutomatically;
@ -34,6 +35,7 @@ public class SponsorBlockSettings {
public static boolean isSponsorBlockEnabled = false;
public static boolean seenGuidelinesPopup = false;
public static boolean isAddNewSegmentEnabled = false;
public static boolean isVotingEnabled = true;
public static boolean showToastWhenSkippedAutomatically = true;
public static boolean countSkips = true;
public static int adjustNewSegmentMillis = 150;
@ -54,6 +56,14 @@ public class SponsorBlockSettings {
return sponsorBlockViewedUrl + "?UUID=" + UUID;
}
public static String getSponsorBlockVoteUrl(String uuid, String userId, int type) {
return sponsorBlockVoteUrl + "?UUID=" + uuid + "&userID=" + userId + "&type=" + type;
}
public static String getSponsorBlockVoteUrl(String uuid, String userId, String category) {
return sponsorBlockVoteUrl + "?UUID=" + uuid + "&userID=" + userId + "&category=" + category;
}
public static SharedPreferences getPreferences(Context context) {
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}
@ -73,20 +83,26 @@ public class SponsorBlockSettings {
if (!isSponsorBlockEnabled) {
SkipSegmentView.hide();
NewSegmentHelperLayout.hide();
SponsorBlockUtils.hideButton();
SponsorBlockUtils.hideShieldButton();
PlayerController.sponsorSegmentsOfCurrentVideo = null;
} else if (/*isAddNewSegmentEnabled*/false) {
SponsorBlockUtils.showButton();
SponsorBlockUtils.showShieldButton();
}
isAddNewSegmentEnabled = preferences.getBoolean(PREFERENCES_KEY_NEW_SEGMENT_ENABLED, isAddNewSegmentEnabled);
if (!/*isAddNewSegmentEnabled*/false) {
NewSegmentHelperLayout.hide();
SponsorBlockUtils.hideButton();
SponsorBlockUtils.hideShieldButton();
} else {
SponsorBlockUtils.showButton();
SponsorBlockUtils.showShieldButton();
}
isVotingEnabled = preferences.getBoolean(PREFERENCES_KEY_VOTING_ENABLED, isVotingEnabled);
if (!isVotingEnabled)
SponsorBlockUtils.hideVoteButton();
else
SponsorBlockUtils.showVoteButton();
SegmentBehaviour[] possibleBehaviours = SegmentBehaviour.values();
final ArrayList<String> enabledCategories = new ArrayList<>(possibleBehaviours.length);
for (SegmentInfo segment : SegmentInfo.valuesWithoutPreview()) {

View File

@ -6,6 +6,8 @@ import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
import android.text.Spanned;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
@ -37,7 +39,9 @@ import static pl.jakubweg.PlayerController.VERBOSE;
import static pl.jakubweg.PlayerController.getCurrentVideoId;
import static pl.jakubweg.PlayerController.getLastKnownVideoTime;
import static pl.jakubweg.PlayerController.sponsorSegmentsOfCurrentVideo;
import static pl.jakubweg.SponsorBlockSettings.getSponsorBlockVoteUrl;
import static pl.jakubweg.SponsorBlockSettings.sponsorBlockSkipSegmentsUrl;
import static pl.jakubweg.SponsorBlockSettings.uuid;
import static pl.jakubweg.StringRef.str;
@SuppressWarnings({"LongLogTag"})
@ -56,6 +60,15 @@ public abstract class SponsorBlockUtils {
NewSegmentHelperLayout.toggle();
}
};
public static final View.OnClickListener voteButtonListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (debug) {
Log.d(TAG, "Vote button clicked");
}
SponsorBlockUtils.onVotingClicked(v.getContext());
}
};
private static int shareBtnId = -1;
private static long newSponsorSegmentDialogShownMillis;
private static long newSponsorSegmentStartMillis = -1;
@ -145,8 +158,10 @@ public abstract class SponsorBlockUtils {
new Thread(submitRunnable).start();
}
};
private static boolean isShown = false;
private static boolean isShieldShown = false;
private static boolean isVoteShown = false;
private static WeakReference<ImageView> sponsorBlockBtn = new WeakReference<>(null);
private static WeakReference<ImageView> votingBtn = new WeakReference<>(null);
private static String messageToToast = "";
private static EditByHandSaveDialogListener editByHandSaveDialogListener = new EditByHandSaveDialogListener();
private static final DialogInterface.OnClickListener editByHandDialogListener = new DialogInterface.OnClickListener() {
@ -180,6 +195,36 @@ public abstract class SponsorBlockUtils {
dialog.dismiss();
}
};
private static final DialogInterface.OnClickListener segmentVoteClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final Context context = ((AlertDialog) dialog).getContext();
final SponsorSegment segment = sponsorSegmentsOfCurrentVideo[which];
new AlertDialog.Builder(context) // negative and positive are switched for more intuitive order
.setNegativeButton(str("vote_upvote"), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(context, str("vote_started"), Toast.LENGTH_SHORT).show();
voteForSegment(segment, true, null);
}
})
.setPositiveButton(str("vote_downvote"), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(context, str("vote_started"), Toast.LENGTH_SHORT).show();
voteForSegment(segment, false, null);
}
})
.setNeutralButton(str("vote_category"), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onNewCategorySelect(segment, context);
}
})
.show();
}
};
private static Runnable toastRunnable = new Runnable() {
@Override
public void run() {
@ -250,9 +295,9 @@ public abstract class SponsorBlockUtils {
private SponsorBlockUtils() {
}
public static void showButton() {
if (isShown) return;
isShown = true;
public static void showShieldButton() {
if (isShieldShown) return;
isShieldShown = true;
View i = sponsorBlockBtn.get();
if (i == null) return;
i.setVisibility(VISIBLE);
@ -261,14 +306,33 @@ public abstract class SponsorBlockUtils {
i.invalidate();
}
public static void hideButton() {
if (!isShown) return;
isShown = false;
public static void hideShieldButton() {
if (!isShieldShown) return;
isShieldShown = false;
View i = sponsorBlockBtn.get();
if (i != null)
i.setVisibility(GONE);
}
public static void showVoteButton() {
if (isVoteShown) return;
isVoteShown = true;
View i = votingBtn.get();
if (i == null) return;
i.setVisibility(VISIBLE);
i.bringToFront();
i.requestLayout();
i.invalidate();
}
public static void hideVoteButton() {
if (!isVoteShown) return;
isVoteShown = false;
View i = votingBtn.get();
if (i != null)
i.setVisibility(GONE);
}
@SuppressLint("DefaultLocale")
public static void onMarkLocationClicked(Context context) {
newSponsorSegmentDialogShownMillis = PlayerController.getLastKnownVideoTime();
@ -305,6 +369,43 @@ public abstract class SponsorBlockUtils {
}
}
public static void onVotingClicked(final Context context) {
if (sponsorSegmentsOfCurrentVideo == null || sponsorSegmentsOfCurrentVideo.length == 0) // prevent crashing or empty dialog
return;
CharSequence[] titles = new CharSequence[sponsorSegmentsOfCurrentVideo.length];
for (int i = 0; i < sponsorSegmentsOfCurrentVideo.length; i++) {
SponsorSegment segment = sponsorSegmentsOfCurrentVideo[i];
String start = dateFormatter.format(new Date(segment.start));
String end = dateFormatter.format(new Date(segment.end));
Spanned html = Html.fromHtml(String.format("<b><font color=\"#%06X\">⬤</font> %s<br> %s to %s",
segment.category.color, segment.category.title, start, end));
titles[i] = html;
}
new AlertDialog.Builder(context)
.setItems(titles, segmentVoteClickListener)
.show();
}
private static void onNewCategorySelect(final SponsorSegment segment, Context context) {
final SponsorBlockSettings.SegmentInfo[] values = SponsorBlockSettings.SegmentInfo.valuesWithoutPreview();
CharSequence[] titles = new CharSequence[values.length];
for (int i = 0; i < values.length; i++) {
titles[i] = values[i].getTitleWithDot();
}
new AlertDialog.Builder(context)
.setTitle(str("new_segment_choose_category"))
.setItems(titles, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
voteForSegment(segment, false, values[which].key);
}
})
.show();
}
@SuppressLint("DefaultLocale")
public static void onPreviewClicked(Context context) {
if (newSponsorSegmentStartMillis >= 0 && newSponsorSegmentStartMillis < newSponsorSegmentEndMillis) {
@ -416,7 +517,46 @@ public abstract class SponsorBlockUtils {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.getInputStream().close();
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void voteForSegment(SponsorSegment segment, boolean upvote, String category) {
messageToToast = null;
try {
String voteUrl = category == null
? getSponsorBlockVoteUrl(segment.UUID, uuid, upvote ? 1 : 0)
: getSponsorBlockVoteUrl(segment.UUID, uuid, category);
URL url = new URL(voteUrl);
Log.d("sponsorblock", "requesting: " + url.getPath());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
switch (connection.getResponseCode()) {
default:
messageToToast = String.format(str("vote_failed_unknown_error"), connection.getResponseCode(), connection.getResponseMessage());
break;
case 429:
messageToToast = str("vote_failed_rate_limit");
break;
case 403:
messageToToast = str("vote_failed_forbidden");
break;
case 409:
messageToToast = str("vote_failed_duplicate");
break;
case 200:
messageToToast = str("vote_succeeded");
break;
}
Log.i(TAG, "Voted for segment with status: " + connection.getResponseCode() + ", " + messageToToast);
new Handler(Looper.getMainLooper()).post(toastRunnable);
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();

View File

@ -0,0 +1,133 @@
package pl.jakubweg;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
import java.lang.ref.WeakReference;
import static fi.razerman.youtube.XGlobals.debug;
public class VotingButton {
static String TAG = "VOTING";
static RelativeLayout _youtubeControlsLayout;
static WeakReference<ImageView> _votingButton = new WeakReference<>(null);
static int fadeDurationFast;
static int fadeDurationScheduled;
static Animation fadeIn;
static Animation fadeOut;
static boolean isShowing;
public static void initialize(Object viewStub) {
try {
if(debug){
Log.d(TAG, "initializing voting button");
}
_youtubeControlsLayout = (RelativeLayout) viewStub;
initButtonVisibilitySettings();
ImageView imageView = (ImageView)_youtubeControlsLayout
.findViewById(getIdentifier("voting_button", "id"));
if (debug && imageView == null){
Log.d(TAG, "Couldn't find imageView with tag \"voting_button\"");
}
if (imageView == null) return;
imageView.setOnClickListener(SponsorBlockUtils.voteButtonListener);
_votingButton = new WeakReference<>(imageView);
// Animations
fadeDurationFast = getInteger("fade_duration_fast");
fadeDurationScheduled = getInteger("fade_duration_scheduled");
fadeIn = getAnimation("fade_in");
fadeIn.setDuration(fadeDurationFast);
fadeOut = getAnimation("fade_out");
fadeOut.setDuration(fadeDurationScheduled);
isShowing = true;
changeVisibilityImmediate(false);
}
catch (Exception ex) {
Log.e(TAG, "Unable to set RelativeLayout", ex);
}
}
public static void changeVisibilityImmediate(boolean visible) {
changeVisibility(visible, true);
}
public static void changeVisibilityNegatedImmediate(boolean visible) {
changeVisibility(!visible, true);
}
public static void changeVisibility(boolean visible) {
changeVisibility(visible, false);
}
public static void changeVisibility(boolean visible, boolean immediate) {
if (isShowing == visible) return;
isShowing = visible;
ImageView iView = _votingButton.get();
if (_youtubeControlsLayout == null || iView == null) return;
if (visible && shouldBeShown()) {
if (debug) {
Log.d(TAG, "Fading in");
}
iView.setVisibility(View.VISIBLE);
if (!immediate)
iView.startAnimation(fadeIn);
return;
}
if (iView.getVisibility() == View.VISIBLE) {
if (debug) {
Log.d(TAG, "Fading out");
}
if (!immediate)
iView.startAnimation(fadeOut);
iView.setVisibility(shouldBeShown() ? View.INVISIBLE : View.GONE);
}
}
private static boolean shouldBeShown() {
return SponsorBlockSettings.isVotingEnabled && SponsorBlockSettings.isSponsorBlockEnabled;
}
private static void initButtonVisibilitySettings() {
Context context = YouTubeTikTokRoot_Application.getAppContext();
if(context == null){
Log.e(TAG, "context is null");
SponsorBlockSettings.isSponsorBlockEnabled = false;
SponsorBlockSettings.isVotingEnabled = false;
return;
}
SharedPreferences sharedPreferences = context.getSharedPreferences(SponsorBlockSettings.PREFERENCES_NAME, Context.MODE_PRIVATE);
SponsorBlockSettings.isSponsorBlockEnabled = sharedPreferences.getBoolean(SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, false);
SponsorBlockSettings.isVotingEnabled = sharedPreferences.getBoolean(SponsorBlockSettings.PREFERENCES_KEY_VOTING_ENABLED, false);
}
//region Helpers
private static int getIdentifier(String name, String defType) {
Context context = YouTubeTikTokRoot_Application.getAppContext();
return context.getResources().getIdentifier(name, defType, context.getPackageName());
}
private static int getInteger(String name) {
return YouTubeTikTokRoot_Application.getAppContext().getResources().getInteger(getIdentifier(name, "integer"));
}
private static Animation getAnimation(String name) {
return AnimationUtils.loadAnimation(YouTubeTikTokRoot_Application.getAppContext(), getIdentifier(name, "anim"));
}
//endregion
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,6c0,-0.55 -0.45,-1 -1,-1L5.82,5l0.66,-3.18 0.02,-0.23c0,-0.31 -0.13,-0.59 -0.33,-0.8L5.38,0 0.44,4.94C0.17,5.21 0,5.59 0,6v6.5c0,0.83 0.67,1.5 1.5,1.5h6.75c0.62,0 1.15,-0.38 1.38,-0.91l2.26,-5.29c0.07,-0.17 0.11,-0.36 0.11,-0.55L12,6zM22.5,10h-6.75c-0.62,0 -1.15,0.38 -1.38,0.91l-2.26,5.29c-0.07,0.17 -0.11,0.36 -0.11,0.55L12,18c0,0.55 0.45,1 1,1h5.18l-0.66,3.18 -0.02,0.24c0,0.31 0.13,0.59 0.33,0.8l0.79,0.78 4.94,-4.94c0.27,-0.27 0.44,-0.65 0.44,-1.06v-6.5c0,-0.83 -0.67,-1.5 -1.5,-1.5z"/>
</vector>

View File

@ -143,6 +143,8 @@
<string name="enable_sb_sum">Switch this on for very cool sponsor segments skipping</string>
<string name="enable_segmadding">Enable new segment adding</string>
<string name="enable_segmadding_sum">Switch this on to enable experimental segment adding (has button visibility issues).</string>
<string name="enable_voting">Enable voting</string>
<string name="enable_voting_sum">Switch this on to enable voting.</string>
<string name="diff_segments">What to do with different segments</string>
<string name="general">General</string>
<string name="general_skiptoast">Show a toast when skipping segment automatically</string>
@ -188,6 +190,16 @@
<string name="submit_succeeded">Segment submitted successfully</string>
<string name="submit_started">Submitting segment…</string>
<string name="vote_failed_unknown_error" formatted="false">Unable to vote for segment: Status: %d %s</string>
<string name="vote_failed_rate_limit">Can\'t vote for segment.\nRate Limited (Too many from the same user or IP)</string>
<string name="vote_failed_forbidden">Can\'t vote for segment.\nA moderator has decided that this segment is correct</string>
<string name="vote_failed_duplicate">Can\'t vote for segment.\nDuplicate</string>
<string name="vote_succeeded">Voted successfully</string>
<string name="vote_started">Voting for segment…</string>
<string name="vote_upvote">Upvote</string>
<string name="vote_downvote">Downvote</string>
<string name="vote_category">Change category</string>
<string name="new_segment_choose_category">Choose the segment category</string>
<string name="new_segment_disabled_category">You\'ve disabled this category in the settings, enable it to be able to submit</string>
<string name="new_segment_title">New Sponsor Block segment</string>