mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 08:30:44 +00:00
Reworked switching to semitones
Using an expandable Tab-like component instead of a combobox
This commit is contained in:
parent
621b38c98b
commit
4b06536582
@ -25,7 +25,6 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
@ -37,7 +36,6 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import androidx.annotation.AttrRes
|
|
||||||
import androidx.annotation.Nullable
|
import androidx.annotation.Nullable
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
@ -77,6 +75,7 @@ import org.schabi.newpipe.local.feed.item.StreamItem
|
|||||||
import org.schabi.newpipe.local.feed.service.FeedLoadService
|
import org.schabi.newpipe.local.feed.service.FeedLoadService
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||||
import org.schabi.newpipe.util.DeviceUtils
|
import org.schabi.newpipe.util.DeviceUtils
|
||||||
|
import org.schabi.newpipe.util.DrawableResolver.Companion.resolveDrawable
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import org.schabi.newpipe.util.NavigationHelper
|
import org.schabi.newpipe.util.NavigationHelper
|
||||||
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams
|
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams
|
||||||
@ -579,19 +578,6 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
|||||||
lastNewItemsCount = highlightCount
|
lastNewItemsCount = highlightCount
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? {
|
|
||||||
return androidx.core.content.ContextCompat.getDrawable(
|
|
||||||
context,
|
|
||||||
android.util.TypedValue().apply {
|
|
||||||
context.theme.resolveAttribute(
|
|
||||||
attrResId,
|
|
||||||
this,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}.resourceId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showNewItemsLoaded() {
|
private fun showNewItemsLoaded() {
|
||||||
tryGetNewItemsLoadedButton()?.clearAnimation()
|
tryGetNewItemsLoadedButton()?.clearAnimation()
|
||||||
tryGetNewItemsLoadedButton()
|
tryGetNewItemsLoadedButton()
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package org.schabi.newpipe.player.helper;
|
package org.schabi.newpipe.player.helper;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
|
||||||
import static org.schabi.newpipe.player.Player.DEBUG;
|
import static org.schabi.newpipe.player.Player.DEBUG;
|
||||||
|
import static org.schabi.newpipe.util.DrawableResolver.resolveDrawable;
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -22,8 +26,11 @@ import androidx.preference.PreferenceManager;
|
|||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding;
|
import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding;
|
||||||
|
import org.schabi.newpipe.player.Player;
|
||||||
import org.schabi.newpipe.util.SliderStrategy;
|
import org.schabi.newpipe.util.SliderStrategy;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.DoubleConsumer;
|
import java.util.function.DoubleConsumer;
|
||||||
@ -40,6 +47,9 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
private static final double MIN_PLAYBACK_VALUE = 0.10f;
|
private static final double MIN_PLAYBACK_VALUE = 0.10f;
|
||||||
private static final double MAX_PLAYBACK_VALUE = 3.00f;
|
private static final double MAX_PLAYBACK_VALUE = 3.00f;
|
||||||
|
|
||||||
|
private static final boolean PITCH_CTRL_MODE_PERCENT = false;
|
||||||
|
private static final boolean PITCH_CTRL_MODE_SEMITONE = true;
|
||||||
|
|
||||||
private static final double STEP_1_PERCENT_VALUE = 0.01f;
|
private static final double STEP_1_PERCENT_VALUE = 0.01f;
|
||||||
private static final double STEP_5_PERCENT_VALUE = 0.05f;
|
private static final double STEP_5_PERCENT_VALUE = 0.05f;
|
||||||
private static final double STEP_10_PERCENT_VALUE = 0.10f;
|
private static final double STEP_10_PERCENT_VALUE = 0.10f;
|
||||||
@ -188,6 +198,22 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
1,
|
1,
|
||||||
this::onTempoSliderUpdated);
|
this::onTempoSliderUpdated);
|
||||||
|
|
||||||
|
// Pitch
|
||||||
|
binding.pitchToogleControlModes.setOnClickListener(v -> {
|
||||||
|
final boolean isCurrentlyVisible =
|
||||||
|
binding.pitchControlModeTabs.getVisibility() == View.GONE;
|
||||||
|
binding.pitchControlModeTabs.setVisibility(isCurrentlyVisible
|
||||||
|
? View.VISIBLE
|
||||||
|
: View.GONE);
|
||||||
|
animateRotation(binding.pitchToogleControlModes,
|
||||||
|
Player.DEFAULT_CONTROLS_DURATION,
|
||||||
|
isCurrentlyVisible ? 180 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
getPitchControlModeComponentMappings()
|
||||||
|
.forEach(this::setupPitchControlModeTextView);
|
||||||
|
changePitchControlMode(isCurrentPitchControlModeSemitone());
|
||||||
|
|
||||||
// Pitch - Percent
|
// Pitch - Percent
|
||||||
setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE);
|
setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE);
|
||||||
setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE);
|
setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE);
|
||||||
@ -249,13 +275,6 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
skipSilence = isChecked;
|
skipSilence = isChecked;
|
||||||
updateCallback();
|
updateCallback();
|
||||||
});
|
});
|
||||||
|
|
||||||
bindCheckboxWithBoolPref(
|
|
||||||
binding.adjustBySemitonesCheckbox,
|
|
||||||
R.string.playback_adjust_by_semitones_key,
|
|
||||||
false,
|
|
||||||
this::showPitchSemitonesOrPercent
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setText(
|
private void setText(
|
||||||
@ -291,17 +310,114 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupStepTextView(
|
private void setupPitchControlModeTextView(
|
||||||
final TextView textView,
|
final boolean semitones,
|
||||||
final double stepSizeValue
|
final TextView textView
|
||||||
) {
|
) {
|
||||||
setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue)
|
textView.setOnClickListener(view -> {
|
||||||
.setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue));
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
.edit()
|
||||||
|
.putBoolean(getString(R.string.playback_adjust_by_semitones_key), semitones)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
changePitchControlMode(semitones);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAndUpdateStepSize(final double newStepSize) {
|
private Map<Boolean, TextView> getPitchControlModeComponentMappings() {
|
||||||
this.stepSize = newStepSize;
|
final Map<Boolean, TextView> mappings = new HashMap<>();
|
||||||
|
mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent);
|
||||||
|
mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone);
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changePitchControlMode(final boolean semitones) {
|
||||||
|
// Bring all textviews into a normal state
|
||||||
|
final Map<Boolean, TextView> pitchCtrlModeComponentMapping =
|
||||||
|
getPitchControlModeComponentMappings();
|
||||||
|
pitchCtrlModeComponentMapping.forEach((v, textView) -> textView.setBackground(
|
||||||
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
|
||||||
|
|
||||||
|
// Mark the selected textview
|
||||||
|
final TextView textView = pitchCtrlModeComponentMapping.get(semitones);
|
||||||
|
if (textView != null) {
|
||||||
|
textView.setBackground(new LayerDrawable(new Drawable[]{
|
||||||
|
resolveDrawable(requireContext(), R.attr.dashed_border),
|
||||||
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show or hide component
|
||||||
|
binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE);
|
||||||
|
binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
if (semitones) {
|
||||||
|
// Recalculate pitch percent when changing to semitone
|
||||||
|
// (as it could be an invalid semitone value)
|
||||||
|
final double newPitchPercent = calcValidPitch(pitchPercent);
|
||||||
|
|
||||||
|
// If the values differ set the new pitch
|
||||||
|
if (this.pitchPercent != newPitchPercent) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: "
|
||||||
|
+ "currentPitchPercent = " + pitchPercent + ", "
|
||||||
|
+ "newPitchPercent = " + newPitchPercent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.onPitchPercentSliderUpdated(newPitchPercent);
|
||||||
|
updateCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCurrentPitchControlModeSemitone() {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
.getBoolean(
|
||||||
|
getString(R.string.playback_adjust_by_semitones_key),
|
||||||
|
PITCH_CTRL_MODE_PERCENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupStepTextView(
|
||||||
|
final double stepSizeValue,
|
||||||
|
final TextView textView
|
||||||
|
) {
|
||||||
|
setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue);
|
||||||
|
textView.setOnClickListener(view -> {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
.edit()
|
||||||
|
.putFloat(getString(R.string.adjustment_step_key), (float) stepSizeValue)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
setStepSizeToUI(stepSizeValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Double, TextView> getStepSizeComponentMappings() {
|
||||||
|
final Map<Double, TextView> mappings = new HashMap<>();
|
||||||
|
mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent);
|
||||||
|
mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent);
|
||||||
|
mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent);
|
||||||
|
mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent);
|
||||||
|
mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent);
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStepSizeToUI(final double newStepSize) {
|
||||||
|
// Bring all textviews into a normal state
|
||||||
|
final Map<Double, TextView> stepSiteComponentMapping = getStepSizeComponentMappings();
|
||||||
|
stepSiteComponentMapping.forEach((v, textView) -> textView.setBackground(
|
||||||
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
|
||||||
|
|
||||||
|
// Mark the selected textview
|
||||||
|
final TextView textView = stepSiteComponentMapping.get(newStepSize);
|
||||||
|
if (textView != null) {
|
||||||
|
textView.setBackground(new LayerDrawable(new Drawable[]{
|
||||||
|
resolveDrawable(requireContext(), R.attr.dashed_border),
|
||||||
|
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind to the corresponding control components
|
||||||
binding.tempoStepUp.setText(getStepUpPercentString(newStepSize));
|
binding.tempoStepUp.setText(getStepUpPercentString(newStepSize));
|
||||||
binding.tempoStepDown.setText(getStepDownPercentString(newStepSize));
|
binding.tempoStepDown.setText(getStepDownPercentString(newStepSize));
|
||||||
|
|
||||||
@ -345,29 +461,6 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPitchSemitonesOrPercent(final boolean semitones) {
|
|
||||||
binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE);
|
|
||||||
binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
if (semitones) {
|
|
||||||
// Recalculate pitch percent when changing to semitone
|
|
||||||
// (as it could be an invalid semitone value)
|
|
||||||
final double newPitchPercent = calcValidPitch(pitchPercent);
|
|
||||||
|
|
||||||
// If the values differ set the new pitch
|
|
||||||
if (this.pitchPercent != newPitchPercent) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: "
|
|
||||||
+ "currentPitchPercent = " + pitchPercent + ", "
|
|
||||||
+ "newPitchPercent = " + newPitchPercent
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.onPitchPercentSliderUpdated(newPitchPercent);
|
|
||||||
updateCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Sliders
|
// Sliders
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -447,7 +540,7 @@ public class PlaybackParameterDialog extends DialogFragment {
|
|||||||
final double calcPitch =
|
final double calcPitch =
|
||||||
Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch));
|
Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch));
|
||||||
|
|
||||||
if (!binding.adjustBySemitonesCheckbox.isChecked()) {
|
if (!isCurrentPitchControlModeSemitone()) {
|
||||||
return calcPitch;
|
return calcPitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package org.schabi.newpipe.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.annotation.AttrRes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for resolving [Drawables](Drawable)
|
||||||
|
*/
|
||||||
|
class DrawableResolver {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? {
|
||||||
|
return androidx.core.content.ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
android.util.TypedValue().apply {
|
||||||
|
context.theme.resolveAttribute(
|
||||||
|
attrResId,
|
||||||
|
this,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}.resourceId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -146,11 +146,59 @@
|
|||||||
android:textColor="?attr/colorAccent"
|
android:textColor="?attr/colorAccent"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/pitchToogleControlModes"
|
||||||
|
android:layout_width="22dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:layout_below="@id/separatorPitch"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:srcCompat="@drawable/ic_expand_more"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/pitchControlModeTabs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:layout_below="@id/pitchControlText"
|
||||||
|
android:layout_marginStart="22dp"
|
||||||
|
android:layout_marginEnd="22dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<org.schabi.newpipe.views.NewPipeTextView
|
||||||
|
android:id="@+id/pitchControlModePercent"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/percent"
|
||||||
|
android:textColor="?attr/colorAccent" />
|
||||||
|
|
||||||
|
<org.schabi.newpipe.views.NewPipeTextView
|
||||||
|
android:id="@+id/pitchControlModeSemitone"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/semitone"
|
||||||
|
android:textColor="?attr/colorAccent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/pitchControlContainer"
|
android:id="@+id/pitchControlContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/pitchControlText"
|
android:layout_below="@id/pitchControlModeTabs"
|
||||||
android:layout_marginTop="1dp">
|
android:layout_marginTop="1dp">
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
@ -471,15 +519,6 @@
|
|||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:text="@string/skip_silence_checkbox" />
|
android:text="@string/skip_silence_checkbox" />
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/adjustBySemitonesCheckbox"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:checked="false"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:text="@string/adjust_by_semitones_checkbox" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- END HERE -->
|
<!-- END HERE -->
|
||||||
|
@ -501,6 +501,8 @@
|
|||||||
<string name="playback_step">Step</string>
|
<string name="playback_step">Step</string>
|
||||||
<string name="playback_tempo_step">Tempo step</string>
|
<string name="playback_tempo_step">Tempo step</string>
|
||||||
<string name="playback_reset">Reset</string>
|
<string name="playback_reset">Reset</string>
|
||||||
|
<string name="percent">Percent</string>
|
||||||
|
<string name="semitone">Semitone</string>
|
||||||
<!-- GDPR dialog -->
|
<!-- GDPR dialog -->
|
||||||
<string name="start_accept_privacy_policy">In order to comply with the European General Data Protection Regulation (GDPR), we hereby draw your attention to NewPipe\'s privacy policy. Please read it carefully.
|
<string name="start_accept_privacy_policy">In order to comply with the European General Data Protection Regulation (GDPR), we hereby draw your attention to NewPipe\'s privacy policy. Please read it carefully.
|
||||||
\nYou must accept it to send us the bug report.</string>
|
\nYou must accept it to send us the bug report.</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user