From 652d50173e64efd01eeca1c9454d205c2c513b4a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sat, 1 Jan 2022 19:02:03 +0100 Subject: [PATCH 01/18] Major refactoring of PlaybackParameterDialog * Removed/Renamed methods * Use ``IcePick`` * Better structuring * Keep skipSilence when rotating the device (PlayQueueActivity only) --- .../helper/PlaybackParameterDialog.java | 726 ++++++------------ 1 file changed, 224 insertions(+), 502 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 1a55c21c3..b72b7ea7b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -9,10 +9,10 @@ import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.CheckBox; -import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -20,104 +20,88 @@ import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; -import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; +import java.util.Objects; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleFunction; + +import icepick.Icepick; +import icepick.State; + public class PlaybackParameterDialog extends DialogFragment { + private static final String TAG = "PlaybackParameterDialog"; + // Minimum allowable range in ExoPlayer private static final double MINIMUM_PLAYBACK_VALUE = 0.10f; private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; - private static final char STEP_UP_SIGN = '+'; - private static final char STEP_DOWN_SIGN = '-'; - - private static final double STEP_ONE_PERCENT_VALUE = 0.01f; - private static final double STEP_FIVE_PERCENT_VALUE = 0.05f; - private static final double STEP_TEN_PERCENT_VALUE = 0.10f; - private static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f; - private static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f; + 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_10_PERCENT_VALUE = 0.10f; + private static final double STEP_25_PERCENT_VALUE = 0.25f; + private static final double STEP_100_PERCENT_VALUE = 1.00f; private static final double DEFAULT_TEMPO = 1.00f; private static final double DEFAULT_PITCH = 1.00f; - private static final int DEFAULT_SEMITONES = 0; - private static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE; + private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE; private static final boolean DEFAULT_SKIP_SILENCE = false; - @NonNull - private static final String TAG = "PlaybackParameterDialog"; - @NonNull - private static final String INITIAL_TEMPO_KEY = "initial_tempo_key"; - @NonNull - private static final String INITIAL_PITCH_KEY = "initial_pitch_key"; - - @NonNull - private static final String TEMPO_KEY = "tempo_key"; - @NonNull - private static final String PITCH_KEY = "pitch_key"; - @NonNull - private static final String STEP_SIZE_KEY = "step_size_key"; - - @NonNull - private final SliderStrategy strategy = new SliderStrategy.Quadratic( - MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE, - /*centerAt=*/1.00f, /*sliderGranularity=*/10000); + private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( + MINIMUM_PLAYBACK_VALUE, + MAXIMUM_PLAYBACK_VALUE, + 1.00f, + 10_000); @Nullable private Callback callback; - private double initialTempo = DEFAULT_TEMPO; - private double initialPitch = DEFAULT_PITCH; - private int initialSemitones = DEFAULT_SEMITONES; - private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; - private double tempo = DEFAULT_TEMPO; - private double pitch = DEFAULT_PITCH; - private int semitones = DEFAULT_SEMITONES; + @State + double initialTempo = DEFAULT_TEMPO; + @State + double initialPitch = DEFAULT_PITCH; + @State + boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; + + @State + double tempo = DEFAULT_TEMPO; + @State + double pitch = DEFAULT_PITCH; + @State + double stepSize = DEFAULT_STEP; + @State + boolean skipSilence = DEFAULT_SKIP_SILENCE; - @Nullable private SeekBar tempoSlider; - @Nullable private TextView tempoCurrentText; - @Nullable private TextView tempoStepDownText; - @Nullable private TextView tempoStepUpText; - @Nullable - private SeekBar pitchSlider; - @Nullable - private TextView pitchCurrentText; - @Nullable - private TextView pitchStepDownText; - @Nullable - private TextView pitchStepUpText; - @Nullable - private SeekBar semitoneSlider; - @Nullable - private TextView semitoneCurrentText; - @Nullable - private TextView semitoneStepDownText; - @Nullable - private TextView semitoneStepUpText; - @Nullable - private CheckBox unhookingCheckbox; - @Nullable - private CheckBox skipSilenceCheckbox; - @Nullable - private CheckBox adjustBySemitonesCheckbox; - public static PlaybackParameterDialog newInstance(final double playbackTempo, - final double playbackPitch, - final boolean playbackSkipSilence, - final Callback callback) { + private SeekBar pitchSlider; + private TextView pitchCurrentText; + private TextView pitchStepDownText; + private TextView pitchStepUpText; + + private CheckBox unhookingCheckbox; + private CheckBox skipSilenceCheckbox; + + public static PlaybackParameterDialog newInstance( + final double playbackTempo, + final double playbackPitch, + final boolean playbackSkipSilence, + final Callback callback + ) { final PlaybackParameterDialog dialog = new PlaybackParameterDialog(); dialog.callback = callback; + dialog.initialTempo = playbackTempo; dialog.initialPitch = playbackPitch; - - dialog.tempo = playbackTempo; - dialog.pitch = playbackPitch; - dialog.semitones = dialog.percentToSemitones(playbackPitch); - dialog.initialSkipSilence = playbackSkipSilence; + + dialog.tempo = dialog.initialTempo; + dialog.pitch = dialog.initialPitch; + dialog.skipSilence = dialog.initialSkipSilence; + return dialog; } @@ -126,7 +110,7 @@ public class PlaybackParameterDialog extends DialogFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(@NonNull final Context context) { + public void onAttach(final Context context) { super.onAttach(context); if (context instanceof Callback) { callback = (Callback) context; @@ -136,28 +120,9 @@ public class PlaybackParameterDialog extends DialogFragment { } @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - assureCorrectAppLanguage(getContext()); - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO); - initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH); - initialSemitones = percentToSemitones(initialPitch); - - tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO); - pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH); - semitones = percentToSemitones(pitch); - } - } - - @Override - public void onSaveInstanceState(@NonNull final Bundle outState) { + public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); - outState.putDouble(INITIAL_TEMPO_KEY, initialTempo); - outState.putDouble(INITIAL_PITCH_KEY, initialPitch); - - outState.putDouble(TEMPO_KEY, getCurrentTempo()); - outState.putDouble(PITCH_KEY, getCurrentPitch()); + Icepick.saveInstanceState(this, outState); } /*////////////////////////////////////////////////////////////////////////// @@ -168,20 +133,28 @@ public class PlaybackParameterDialog extends DialogFragment { @Override public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); + Icepick.restoreInstanceState(this, savedInstanceState); + final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); - setupControlViews(view); + initUI(view); + initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) .setView(view) .setCancelable(true) - .setNegativeButton(R.string.cancel, (dialogInterface, i) -> - setPlaybackParameters(initialTempo, initialPitch, - initialSemitones, initialSkipSilence)) - .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> - setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, - DEFAULT_SEMITONES, DEFAULT_SKIP_SILENCE)) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> - setCurrentPlaybackParameters()); + .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { + setAndUpdateTempo(initialTempo); + setAndUpdatePitch(initialPitch); + setAndUpdateSkipSilence(initialSkipSilence); + updateCallback(); + }) + .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> { + setAndUpdateTempo(DEFAULT_TEMPO); + setAndUpdatePitch(DEFAULT_PITCH); + setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE); + updateCallback(); + }) + .setPositiveButton(R.string.ok, (dialogInterface, i) -> updateCallback()); return dialogBuilder.create(); } @@ -190,353 +163,171 @@ public class PlaybackParameterDialog extends DialogFragment { // Control Views //////////////////////////////////////////////////////////////////////////*/ - private void setupControlViews(@NonNull final View rootView) { - setupHookingControl(rootView); - setupSkipSilenceControl(rootView); - setupAdjustBySemitonesControl(rootView); + private void initUI(@NonNull final View rootView) { + // Tempo + tempoSlider = Objects.requireNonNull(rootView.findViewById(R.id.tempoSeekbar)); + tempoCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.tempoCurrentText)); + tempoStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepUp)); + tempoStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepDown)); - setupTempoControl(rootView); - setupPitchControl(rootView); - setupSemitoneControl(rootView); + setText(rootView, R.id.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); + setText(rootView, R.id.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); - togglePitchSliderType(rootView); + // Pitch + pitchSlider = Objects.requireNonNull(rootView.findViewById(R.id.pitchSeekbar)); + pitchCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.pitchCurrentText)); + pitchStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepUp)); + pitchStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepDown)); - setupStepSizeSelector(rootView); + setText(rootView, R.id.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); + setText(rootView, R.id.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); + + // Steps + setupStepTextView(rootView, R.id.stepSizeOnePercent, STEP_1_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeFivePercent, STEP_5_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeTenPercent, STEP_10_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); + + // Bottom controls + unhookingCheckbox = + Objects.requireNonNull(rootView.findViewById(R.id.unhookCheckbox)); + skipSilenceCheckbox = + Objects.requireNonNull(rootView.findViewById(R.id.skipSilenceCheckbox)); } - private void togglePitchSliderType(@NonNull final View rootView) { - final RelativeLayout pitchControl = rootView.findViewById(R.id.pitchControl); - final RelativeLayout semitoneControl = rootView.findViewById(R.id.semitoneControl); - - final View separatorStepSizeSelector = - rootView.findViewById(R.id.separatorStepSizeSelector); - final RelativeLayout.LayoutParams params = - (RelativeLayout.LayoutParams) separatorStepSizeSelector.getLayoutParams(); - if (pitchControl != null && semitoneControl != null && unhookingCheckbox != null) { - if (getCurrentAdjustBySemitones()) { - // replaces pitchControl slider with semitoneControl slider - pitchControl.setVisibility(View.GONE); - semitoneControl.setVisibility(View.VISIBLE); - params.addRule(RelativeLayout.BELOW, R.id.semitoneControl); - - // forces unhook for semitones - unhookingCheckbox.setChecked(true); - unhookingCheckbox.setEnabled(false); - - setupTempoStepSizeSelector(rootView); - } else { - semitoneControl.setVisibility(View.GONE); - pitchControl.setVisibility(View.VISIBLE); - params.addRule(RelativeLayout.BELOW, R.id.pitchControl); - - // (re)enables hooking selection - unhookingCheckbox.setEnabled(true); - setupCombinedStepSizeSelector(rootView); - } - } + private TextView setText( + final TextView textView, + final DoubleFunction formatter, + final double value + ) { + Objects.requireNonNull(textView).setText(formatter.apply(value)); + return textView; } - private void setupTempoControl(@NonNull final View rootView) { - tempoSlider = rootView.findViewById(R.id.tempoSeekbar); - final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText); - final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText); - tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText); - tempoStepUpText = rootView.findViewById(R.id.tempoStepUp); - tempoStepDownText = rootView.findViewById(R.id.tempoStepDown); - - if (tempoCurrentText != null) { - tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo)); - } - if (tempoMaximumText != null) { - tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE)); - } - if (tempoMinimumText != null) { - tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE)); - } - - if (tempoSlider != null) { - tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); - tempoSlider.setProgress(strategy.progressOf(tempo)); - tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener()); - } + private TextView setText( + final View rootView, + @IdRes final int idRes, + final DoubleFunction formatter, + final double value + ) { + final TextView textView = rootView.findViewById(idRes); + setText(textView, formatter, value); + return textView; } - private void setupPitchControl(@NonNull final View rootView) { - pitchSlider = rootView.findViewById(R.id.pitchSeekbar); - final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText); - final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText); - pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText); - pitchStepDownText = rootView.findViewById(R.id.pitchStepDown); - pitchStepUpText = rootView.findViewById(R.id.pitchStepUp); - - if (pitchCurrentText != null) { - pitchCurrentText.setText(PlayerHelper.formatPitch(pitch)); - } - if (pitchMaximumText != null) { - pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE)); - } - if (pitchMinimumText != null) { - pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE)); - } - - if (pitchSlider != null) { - pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); - pitchSlider.setProgress(strategy.progressOf(pitch)); - pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener()); - } + private void setupStepTextView( + final View rootView, + @IdRes final int idRes, + final double stepSizeValue + ) { + setText(rootView, idRes, PlaybackParameterDialog::getPercentString, stepSizeValue) + .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); } - private void setupSemitoneControl(@NonNull final View rootView) { - semitoneSlider = rootView.findViewById(R.id.semitoneSeekbar); - semitoneCurrentText = rootView.findViewById(R.id.semitoneCurrentText); - semitoneStepDownText = rootView.findViewById(R.id.semitoneStepDown); - semitoneStepUpText = rootView.findViewById(R.id.semitoneStepUp); + private void initUIData() { + // Tempo + tempoSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + setAndUpdateTempo(tempo); + tempoSlider.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); - if (semitoneCurrentText != null) { - semitoneCurrentText.setText(getSignedSemitonesString(semitones)); - } + registerOnStepClickListener( + tempoStepDownText, tempo, -1, this::onTempoSliderUpdated); + registerOnStepClickListener( + tempoStepUpText, tempo, 1, this::onTempoSliderUpdated); - if (semitoneSlider != null) { - setSemitoneSlider(semitones); - semitoneSlider.setOnSeekBarChangeListener(getOnSemitoneChangedListener()); - } + // Pitch + pitchSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + setAndUpdatePitch(pitch); + pitchSlider.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); - } + registerOnStepClickListener( + pitchStepDownText, pitch, -1, this::onPitchSliderUpdated); + registerOnStepClickListener( + pitchStepUpText, pitch, 1, this::onPitchSliderUpdated); - private void setupHookingControl(@NonNull final View rootView) { - unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); - if (unhookingCheckbox != null) { - // restores whether pitch and tempo are unhooked or not - unhookingCheckbox.setChecked(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_unhook_key), true)); + // Steps + setAndUpdateStepSize(stepSize); - unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - // saves whether pitch and tempo are unhooked or not - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .edit() - .putBoolean(getString(R.string.playback_unhook_key), isChecked) - .apply(); - - if (!isChecked) { - // when unchecked, slides back to the minimum of current tempo or pitch - final double minimum = Math.min(getCurrentPitch(), getCurrentTempo()); - setSliders(minimum); - setCurrentPlaybackParameters(); - } - }); - } - } - - private void setupSkipSilenceControl(@NonNull final View rootView) { - skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox); - if (skipSilenceCheckbox != null) { - skipSilenceCheckbox.setChecked(initialSkipSilence); - skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> - setCurrentPlaybackParameters()); - } - } - - private void setupAdjustBySemitonesControl(@NonNull final View rootView) { - adjustBySemitonesCheckbox = rootView.findViewById(R.id.adjustBySemitonesCheckbox); - if (adjustBySemitonesCheckbox != null) { - // restores whether semitone adjustment is used or not - adjustBySemitonesCheckbox.setChecked(PreferenceManager + // Bottom controls + // restore whether pitch and tempo are unhooked or not + unhookingCheckbox.setChecked(PreferenceManager .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_adjust_by_semitones_key), true)); + .getBoolean(getString(R.string.playback_unhook_key), true)); - // stores whether semitone adjustment is used or not - adjustBySemitonesCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - PreferenceManager.getDefaultSharedPreferences(requireContext()) + unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + // save whether pitch and tempo are unhooked or not + PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() - .putBoolean(getString(R.string.playback_adjust_by_semitones_key), isChecked) + .putBoolean(getString(R.string.playback_unhook_key), isChecked) .apply(); - togglePitchSliderType(rootView); - if (isChecked) { - setPlaybackParameters( - getCurrentTempo(), - getCurrentPitch(), - Integer.min(12, - Integer.max(-12, percentToSemitones(getCurrentPitch()) - )), - getCurrentSkipSilence() - ); - setSemitoneSlider(Integer.min(12, - Integer.max(-12, percentToSemitones(getCurrentPitch())) - )); - } else { - setPlaybackParameters( - getCurrentTempo(), - semitonesToPercent(getCurrentSemitones()), - getCurrentSemitones(), - getCurrentSkipSilence() - ); - setPitchSlider(semitonesToPercent(getCurrentSemitones())); - } - }); - } + + if (!isChecked) { + // when unchecked, slide back to the minimum of current tempo or pitch + setSliders(Math.min(pitch, tempo)); + } + }); + + setAndUpdateSkipSilence(skipSilence); + skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + skipSilence = isChecked; + updateCallback(); + }); } - private void setupStepSizeSelector(@NonNull final View rootView) { - setStepSize(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP)); - - final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); - final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); - final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent); - final TextView stepSizeTwentyFivePercentText = rootView - .findViewById(R.id.stepSizeTwentyFivePercent); - final TextView stepSizeOneHundredPercentText = rootView - .findViewById(R.id.stepSizeOneHundredPercent); - - if (stepSizeOnePercentText != null) { - stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE)); - stepSizeOnePercentText - .setOnClickListener(view -> setStepSize(STEP_ONE_PERCENT_VALUE)); - } - - if (stepSizeFivePercentText != null) { - stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE)); - stepSizeFivePercentText - .setOnClickListener(view -> setStepSize(STEP_FIVE_PERCENT_VALUE)); - } - - if (stepSizeTenPercentText != null) { - stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE)); - stepSizeTenPercentText - .setOnClickListener(view -> setStepSize(STEP_TEN_PERCENT_VALUE)); - } - - if (stepSizeTwentyFivePercentText != null) { - stepSizeTwentyFivePercentText - .setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE)); - stepSizeTwentyFivePercentText - .setOnClickListener(view -> setStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE)); - } - - if (stepSizeOneHundredPercentText != null) { - stepSizeOneHundredPercentText - .setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE)); - stepSizeOneHundredPercentText - .setOnClickListener(view -> setStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE)); - } + private void registerOnStepClickListener( + final TextView stepTextView, + final double currentValue, + final double direction, // -1 for step down, +1 for step up + final DoubleConsumer newValueConsumer + ) { + stepTextView.setOnClickListener(view -> + newValueConsumer.accept(currentValue * direction) + ); } - private void setupTempoStepSizeSelector(@NonNull final View rootView) { - final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); - if (playbackStepTypeText != null) { - playbackStepTypeText.setText(R.string.playback_tempo_step); - } - setupStepSizeSelector(rootView); + private void setAndUpdateStepSize(final double newStepSize) { + this.stepSize = newStepSize; + + tempoStepUpText.setText(getStepUpPercentString(newStepSize)); + tempoStepDownText.setText(getStepDownPercentString(newStepSize)); + + pitchStepUpText.setText(getStepUpPercentString(newStepSize)); + pitchStepDownText.setText(getStepDownPercentString(newStepSize)); } - private void setupCombinedStepSizeSelector(@NonNull final View rootView) { - final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); - if (playbackStepTypeText != null) { - playbackStepTypeText.setText(R.string.playback_step); - } - setupStepSizeSelector(rootView); - } - - private void setStepSize(final double stepSize) { - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .edit() - .putFloat(getString(R.string.adjustment_step_key), (float) stepSize) - .apply(); - - if (tempoStepUpText != null) { - tempoStepUpText.setText(getStepUpPercentString(stepSize)); - tempoStepUpText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() + stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (tempoStepDownText != null) { - tempoStepDownText.setText(getStepDownPercentString(stepSize)); - tempoStepDownText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() - stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (pitchStepUpText != null) { - pitchStepUpText.setText(getStepUpPercentString(stepSize)); - pitchStepUpText.setOnClickListener(view -> { - onPitchSliderUpdated(getCurrentPitch() + stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (pitchStepDownText != null) { - pitchStepDownText.setText(getStepDownPercentString(stepSize)); - pitchStepDownText.setOnClickListener(view -> { - onPitchSliderUpdated(getCurrentPitch() - stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (semitoneStepDownText != null) { - semitoneStepDownText.setOnClickListener(view -> { - onSemitoneSliderUpdated(getCurrentSemitones() - 1); - setCurrentPlaybackParameters(); - }); - } - - if (semitoneStepUpText != null) { - semitoneStepUpText.setOnClickListener(view -> { - onSemitoneSliderUpdated(getCurrentSemitones() + 1); - setCurrentPlaybackParameters(); - }); - } + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { + this.skipSilence = newSkipSilence; + skipSilenceCheckbox.setChecked(newSkipSilence); } /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ - private SimpleOnSeekBarChangeListener getOnTempoChangedListener() { - return new SimpleOnSeekBarChangeListener() { + private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener( + final DoubleConsumer newValueConsumer + ) { + return new SeekBar.OnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, + public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { - final double currentTempo = strategy.valueOf(progress); - if (fromUser) { - onTempoSliderUpdated(currentTempo); - setCurrentPlaybackParameters(); + if (fromUser) { // this change is first in chain + newValueConsumer.accept(QUADRATIC_STRATEGY.valueOf(progress)); + updateCallback(); } } - }; - } - private SimpleOnSeekBarChangeListener getOnPitchChangedListener() { - return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, - final boolean fromUser) { - final double currentPitch = strategy.valueOf(progress); - if (fromUser) { // this change is first in chain - onPitchSliderUpdated(currentPitch); - setCurrentPlaybackParameters(); - } + public void onStartTrackingTouch(final SeekBar seekBar) { + // Do nothing } - }; - } - private SimpleOnSeekBarChangeListener getOnSemitoneChangedListener() { - return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, - final boolean fromUser) { - // semitone slider supplies values 0 to 24, subtraction by 12 is required - final int currentSemitones = progress - 12; - if (fromUser) { // this change is first in chain - onSemitoneSliderUpdated(currentSemitones); - // line below also saves semitones as pitch percentages - onPitchSliderUpdated(semitonesToPercent(currentSemitones)); - setCurrentPlaybackParameters(); - } + public void onStopTrackingTouch(final SeekBar seekBar) { + // Do nothing } }; } @@ -545,7 +336,7 @@ public class PlaybackParameterDialog extends DialogFragment { if (!unhookingCheckbox.isChecked()) { setSliders(newTempo); } else { - setTempoSlider(newTempo); + setAndUpdateTempo(newTempo); } } @@ -553,109 +344,53 @@ public class PlaybackParameterDialog extends DialogFragment { if (!unhookingCheckbox.isChecked()) { setSliders(newPitch); } else { - setPitchSlider(newPitch); + setAndUpdatePitch(newPitch); } } - private void onSemitoneSliderUpdated(final int newSemitone) { - setSemitoneSlider(newSemitone); - } - private void setSliders(final double newValue) { - setTempoSlider(newValue); - setPitchSlider(newValue); + setAndUpdateTempo(newValue); + setAndUpdatePitch(newValue); } - private void setTempoSlider(final double newTempo) { - if (tempoSlider == null) { - return; - } - tempoSlider.setProgress(strategy.progressOf(newTempo)); + private void setAndUpdateTempo(final double newTempo) { + this.tempo = newTempo; + tempoSlider.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); + setText(tempoCurrentText, PlayerHelper::formatSpeed, tempo); } - private void setPitchSlider(final double newPitch) { - if (pitchSlider == null) { - return; - } - pitchSlider.setProgress(strategy.progressOf(newPitch)); - } - - private void setSemitoneSlider(final int newSemitone) { - if (semitoneSlider == null) { - return; - } - semitoneSlider.setProgress(newSemitone + 12); + private void setAndUpdatePitch(final double newPitch) { + this.pitch = newPitch; + pitchSlider.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); + setText(pitchCurrentText, PlayerHelper::formatPitch, pitch); } /*////////////////////////////////////////////////////////////////////////// // Helper //////////////////////////////////////////////////////////////////////////*/ - private void setCurrentPlaybackParameters() { - if (getCurrentAdjustBySemitones()) { - setPlaybackParameters( - getCurrentTempo(), - semitonesToPercent(getCurrentSemitones()), - getCurrentSemitones(), - getCurrentSkipSilence() - ); - } else { - setPlaybackParameters( - getCurrentTempo(), - getCurrentPitch(), - percentToSemitones(getCurrentPitch()), - getCurrentSkipSilence() + private void updateCallback() { + if (callback == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "Updating callback: " + + "tempo = [" + tempo + "], " + + "pitch = [" + pitch + "], " + + "skipSilence = [" + skipSilence + "]" ); } - } - - private void setPlaybackParameters(final double newTempo, final double newPitch, - final int newSemitones, final boolean skipSilence) { - if (callback != null && tempoCurrentText != null - && pitchCurrentText != null && semitoneCurrentText != null) { - if (DEBUG) { - Log.d(TAG, "Setting playback parameters to " - + "tempo=[" + newTempo + "], " - + "pitch=[" + newPitch + "], " - + "semitones=[" + newSemitones + "]"); - } - - tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo)); - pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch)); - semitoneCurrentText.setText(getSignedSemitonesString(newSemitones)); - callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence); - } - } - - private double getCurrentTempo() { - return tempoSlider == null ? tempo : strategy.valueOf(tempoSlider.getProgress()); - } - - private double getCurrentPitch() { - return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress()); - } - - private int getCurrentSemitones() { - // semitoneSlider is absolute, that's why - 12 - return semitoneSlider == null ? semitones : semitoneSlider.getProgress() - 12; - } - - private boolean getCurrentSkipSilence() { - return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked(); - } - - private boolean getCurrentAdjustBySemitones() { - return adjustBySemitonesCheckbox != null && adjustBySemitonesCheckbox.isChecked(); + callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence); } @NonNull private static String getStepUpPercentString(final double percent) { - return STEP_UP_SIGN + getPercentString(percent); + return '+' + getPercentString(percent); } @NonNull private static String getStepDownPercentString(final double percent) { - return STEP_DOWN_SIGN + getPercentString(percent); + return '-' + getPercentString(percent); } @NonNull @@ -663,21 +398,8 @@ public class PlaybackParameterDialog extends DialogFragment { return PlayerHelper.formatPitch(percent); } - @NonNull - private static String getSignedSemitonesString(final int semitones) { - return semitones > 0 ? "+" + semitones : "" + semitones; - } - public interface Callback { void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, boolean playbackSkipSilence); } - - public double semitonesToPercent(final int inSemitones) { - return Math.pow(2, inSemitones / 12.0); - } - - public int percentToSemitones(final double inPercent) { - return (int) Math.round(12 * Math.log(inPercent) / Math.log(2)); - } } From 4cdf6eda2cf3d9073d9ccad7c339bfb1856189c2 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 28 Feb 2022 20:45:23 +0100 Subject: [PATCH 02/18] Use viewbinding --- .../helper/PlaybackParameterDialog.java | 124 ++++++------------ 1 file changed, 42 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index b72b7ea7b..e1874fec0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -7,12 +7,10 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.util.Log; -import android.view.View; -import android.widget.CheckBox; +import android.view.LayoutInflater; import android.widget.SeekBar; import android.widget.TextView; -import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -20,6 +18,7 @@ import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; import org.schabi.newpipe.util.SliderStrategy; import java.util.Objects; @@ -72,18 +71,7 @@ public class PlaybackParameterDialog extends DialogFragment { @State boolean skipSilence = DEFAULT_SKIP_SILENCE; - private SeekBar tempoSlider; - private TextView tempoCurrentText; - private TextView tempoStepDownText; - private TextView tempoStepUpText; - - private SeekBar pitchSlider; - private TextView pitchCurrentText; - private TextView pitchStepDownText; - private TextView pitchStepUpText; - - private CheckBox unhookingCheckbox; - private CheckBox skipSilenceCheckbox; + private DialogPlaybackParameterBinding binding; public static PlaybackParameterDialog newInstance( final double playbackTempo, @@ -110,7 +98,7 @@ public class PlaybackParameterDialog extends DialogFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(final Context context) { + public void onAttach(@NonNull final Context context) { super.onAttach(context); if (context instanceof Callback) { callback = (Callback) context; @@ -120,7 +108,7 @@ public class PlaybackParameterDialog extends DialogFragment { } @Override - public void onSaveInstanceState(final Bundle outState) { + public void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } @@ -135,12 +123,12 @@ public class PlaybackParameterDialog extends DialogFragment { assureCorrectAppLanguage(getContext()); Icepick.restoreInstanceState(this, savedInstanceState); - final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); - initUI(view); + binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext())); + initUI(); initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) - .setView(view) + .setView(binding.getRoot()) .setCancelable(true) .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { setAndUpdateTempo(initialTempo); @@ -163,37 +151,21 @@ public class PlaybackParameterDialog extends DialogFragment { // Control Views //////////////////////////////////////////////////////////////////////////*/ - private void initUI(@NonNull final View rootView) { + private void initUI() { // Tempo - tempoSlider = Objects.requireNonNull(rootView.findViewById(R.id.tempoSeekbar)); - tempoCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.tempoCurrentText)); - tempoStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepUp)); - tempoStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepDown)); - - setText(rootView, R.id.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); - setText(rootView, R.id.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); + setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); + setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); // Pitch - pitchSlider = Objects.requireNonNull(rootView.findViewById(R.id.pitchSeekbar)); - pitchCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.pitchCurrentText)); - pitchStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepUp)); - pitchStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepDown)); - - setText(rootView, R.id.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); - setText(rootView, R.id.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); + setText(binding.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); + setText(binding.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); // Steps - setupStepTextView(rootView, R.id.stepSizeOnePercent, STEP_1_PERCENT_VALUE); - setupStepTextView(rootView, R.id.stepSizeFivePercent, STEP_5_PERCENT_VALUE); - setupStepTextView(rootView, R.id.stepSizeTenPercent, STEP_10_PERCENT_VALUE); - setupStepTextView(rootView, R.id.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); - setupStepTextView(rootView, R.id.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); - - // Bottom controls - unhookingCheckbox = - Objects.requireNonNull(rootView.findViewById(R.id.unhookCheckbox)); - skipSilenceCheckbox = - Objects.requireNonNull(rootView.findViewById(R.id.skipSilenceCheckbox)); + setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE); + setupStepTextView(binding.stepSizeFivePercent, STEP_5_PERCENT_VALUE); + setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE); + setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); + setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); } private TextView setText( @@ -205,59 +177,47 @@ public class PlaybackParameterDialog extends DialogFragment { return textView; } - private TextView setText( - final View rootView, - @IdRes final int idRes, - final DoubleFunction formatter, - final double value - ) { - final TextView textView = rootView.findViewById(idRes); - setText(textView, formatter, value); - return textView; - } - private void setupStepTextView( - final View rootView, - @IdRes final int idRes, + final TextView textView, final double stepSizeValue ) { - setText(rootView, idRes, PlaybackParameterDialog::getPercentString, stepSizeValue) + setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue) .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); } private void initUIData() { // Tempo - tempoSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); setAndUpdateTempo(tempo); - tempoSlider.setOnSeekBarChangeListener( + binding.tempoSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); registerOnStepClickListener( - tempoStepDownText, tempo, -1, this::onTempoSliderUpdated); + binding.tempoStepDown, tempo, -1, this::onTempoSliderUpdated); registerOnStepClickListener( - tempoStepUpText, tempo, 1, this::onTempoSliderUpdated); + binding.tempoStepUp, tempo, 1, this::onTempoSliderUpdated); // Pitch - pitchSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + binding.pitchSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); setAndUpdatePitch(pitch); - pitchSlider.setOnSeekBarChangeListener( + binding.pitchSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); registerOnStepClickListener( - pitchStepDownText, pitch, -1, this::onPitchSliderUpdated); + binding.pitchStepDown, pitch, -1, this::onPitchSliderUpdated); registerOnStepClickListener( - pitchStepUpText, pitch, 1, this::onPitchSliderUpdated); + binding.pitchStepUp, pitch, 1, this::onPitchSliderUpdated); // Steps setAndUpdateStepSize(stepSize); // Bottom controls // restore whether pitch and tempo are unhooked or not - unhookingCheckbox.setChecked(PreferenceManager + binding.unhookCheckbox.setChecked(PreferenceManager .getDefaultSharedPreferences(requireContext()) .getBoolean(getString(R.string.playback_unhook_key), true)); - unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + binding.unhookCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { // save whether pitch and tempo are unhooked or not PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() @@ -271,7 +231,7 @@ public class PlaybackParameterDialog extends DialogFragment { }); setAndUpdateSkipSilence(skipSilence); - skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { skipSilence = isChecked; updateCallback(); }); @@ -291,16 +251,16 @@ public class PlaybackParameterDialog extends DialogFragment { private void setAndUpdateStepSize(final double newStepSize) { this.stepSize = newStepSize; - tempoStepUpText.setText(getStepUpPercentString(newStepSize)); - tempoStepDownText.setText(getStepDownPercentString(newStepSize)); + binding.tempoStepUp.setText(getStepUpPercentString(newStepSize)); + binding.tempoStepDown.setText(getStepDownPercentString(newStepSize)); - pitchStepUpText.setText(getStepUpPercentString(newStepSize)); - pitchStepDownText.setText(getStepDownPercentString(newStepSize)); + binding.pitchStepUp.setText(getStepUpPercentString(newStepSize)); + binding.pitchStepDown.setText(getStepDownPercentString(newStepSize)); } private void setAndUpdateSkipSilence(final boolean newSkipSilence) { this.skipSilence = newSkipSilence; - skipSilenceCheckbox.setChecked(newSkipSilence); + binding.skipSilenceCheckbox.setChecked(newSkipSilence); } /*////////////////////////////////////////////////////////////////////////// @@ -333,7 +293,7 @@ public class PlaybackParameterDialog extends DialogFragment { } private void onTempoSliderUpdated(final double newTempo) { - if (!unhookingCheckbox.isChecked()) { + if (!binding.unhookCheckbox.isChecked()) { setSliders(newTempo); } else { setAndUpdateTempo(newTempo); @@ -341,7 +301,7 @@ public class PlaybackParameterDialog extends DialogFragment { } private void onPitchSliderUpdated(final double newPitch) { - if (!unhookingCheckbox.isChecked()) { + if (!binding.unhookCheckbox.isChecked()) { setSliders(newPitch); } else { setAndUpdatePitch(newPitch); @@ -355,14 +315,14 @@ public class PlaybackParameterDialog extends DialogFragment { private void setAndUpdateTempo(final double newTempo) { this.tempo = newTempo; - tempoSlider.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); - setText(tempoCurrentText, PlayerHelper::formatSpeed, tempo); + binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); + setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo); } private void setAndUpdatePitch(final double newPitch) { this.pitch = newPitch; - pitchSlider.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); - setText(pitchCurrentText, PlayerHelper::formatPitch, pitch); + binding.pitchSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); + setText(binding.pitchCurrentText, PlayerHelper::formatPitch, pitch); } /*////////////////////////////////////////////////////////////////////////// From 6e0c3804097a1e3749d329c82e8cca3c72d7ec18 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 28 Feb 2022 21:02:43 +0100 Subject: [PATCH 03/18] Remove redundant attributes --- .../res/layout/dialog_playback_parameter.xml | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 862b2ea67..27cf0dbd6 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -39,7 +39,6 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentStart="true" - android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="?attr/selectableItemBackground" android:clickable="true" @@ -57,9 +56,7 @@ android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_toStartOf="@id/tempoStepUp" - android:layout_toLeftOf="@id/tempoStepUp" android:layout_toEndOf="@id/tempoStepDown" - android:layout_toRightOf="@id/tempoStepDown" android:orientation="horizontal"> Date: Mon, 28 Feb 2022 21:04:24 +0100 Subject: [PATCH 04/18] Remove invalid parameters --- app/src/main/res/layout/dialog_playback_parameter.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 27cf0dbd6..87c26b831 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -463,8 +463,6 @@ android:id="@+id/adjustBySemitonesCheckbox" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@id/skipSilenceCheckbox" - android:layout_centerHorizontal="true" android:checked="false" android:clickable="true" android:focusable="true" From a4c083e7f98c535993c9e22d9d3ae99d5c410a0c Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 1 Mar 2022 20:52:48 +0100 Subject: [PATCH 05/18] Rework dialog * De-Duplicated some fields * Use a container for the pitch controls * Name pitch related elements correctly --- .../res/layout/dialog_playback_parameter.xml | 310 +++++++++--------- 1 file changed, 157 insertions(+), 153 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 87c26b831..47394b724 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -29,7 +29,7 @@ @@ -146,203 +146,207 @@ android:textStyle="bold" /> - - + android:layout_marginTop="3dp"> - - + tools:text="-5%" /> + + + + + + + + + + + - - + tools:text="+5%" /> - - - - - - - - - - - - - + android:layout_height="match_parent" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:layout_toStartOf="@+id/pitchSemitoneStepUp" + android:layout_toEndOf="@+id/pitchSemitoneStepDown" + android:orientation="horizontal"> - + + + + + + + + + + + Date: Tue, 1 Mar 2022 21:19:30 +0100 Subject: [PATCH 06/18] Shrunk dialog a bit --- app/src/main/res/layout/dialog_playback_parameter.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 47394b724..1ab9a95e9 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -356,7 +356,7 @@ From dae5aa38a83a892f43a3589a21bada854601273d Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 2 Mar 2022 20:58:14 +0100 Subject: [PATCH 07/18] Fine tuned dialog (no scrollers in fullscreen on 5in phone) --- .../res/layout/dialog_playback_parameter.xml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 1ab9a95e9..75269ffd3 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -31,7 +31,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/tempoControlText" - android:layout_marginTop="3dp" + android:layout_marginTop="1dp" android:orientation="horizontal"> @@ -129,9 +129,9 @@ android:layout_height="1dp" android:layout_below="@id/tempoControl" android:layout_marginStart="12dp" - android:layout_marginTop="6dp" - android:layout_marginEnd="6dp" - android:layout_marginBottom="6dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="12dp" + android:layout_marginBottom="4dp" android:background="?attr/separator_color" /> + android:layout_marginTop="1dp"> @@ -319,7 +319,7 @@ android:layout_height="wrap_content" android:layout_below="@+id/pitchSemitoneCurrentText" android:max="24" - android:paddingBottom="4dp" + android:paddingBottom="2dp" android:progress="12" /> @@ -348,9 +348,9 @@ android:layout_height="1dp" android:layout_below="@+id/pitchControlContainer" android:layout_marginStart="12dp" - android:layout_marginTop="6dp" + android:layout_marginTop="4dp" android:layout_marginEnd="12dp" - android:layout_marginBottom="6dp" + android:layout_marginBottom="4dp" android:background="?attr/separator_color" /> Date: Wed, 2 Mar 2022 21:00:19 +0100 Subject: [PATCH 08/18] Reworked/Implemented PlaybackParameterDialog functionallity * Add support for semitones * Fixed some minor bugs * Improved some methods --- .../helper/PlaybackParameterDialog.java | 320 +++++++++++++----- .../player/helper/PlayerSemitoneHelper.java | 37 ++ 2 files changed, 264 insertions(+), 93 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index e1874fec0..709216ece 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -8,11 +8,14 @@ import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; import android.widget.SeekBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; @@ -22,8 +25,10 @@ import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; import org.schabi.newpipe.util.SliderStrategy; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.DoubleConsumer; import java.util.function.DoubleFunction; +import java.util.function.DoubleSupplier; import icepick.Icepick; import icepick.State; @@ -32,8 +37,8 @@ public class PlaybackParameterDialog extends DialogFragment { private static final String TAG = "PlaybackParameterDialog"; // Minimum allowable range in ExoPlayer - private static final double MINIMUM_PLAYBACK_VALUE = 0.10f; - private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; + private static final double MIN_PLAYBACK_VALUE = 0.10f; + private static final double MAX_PLAYBACK_VALUE = 3.00f; private static final double STEP_1_PERCENT_VALUE = 0.01f; private static final double STEP_5_PERCENT_VALUE = 0.05f; @@ -42,30 +47,42 @@ public class PlaybackParameterDialog extends DialogFragment { private static final double STEP_100_PERCENT_VALUE = 1.00f; private static final double DEFAULT_TEMPO = 1.00f; - private static final double DEFAULT_PITCH = 1.00f; + private static final double DEFAULT_PITCH_PERCENT = 1.00f; private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE; private static final boolean DEFAULT_SKIP_SILENCE = false; private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( - MINIMUM_PLAYBACK_VALUE, - MAXIMUM_PLAYBACK_VALUE, + MIN_PLAYBACK_VALUE, + MAX_PLAYBACK_VALUE, 1.00f, 10_000); + private static final SliderStrategy SEMITONE_STRATEGY = new SliderStrategy() { + @Override + public int progressOf(final double value) { + return PlayerSemitoneHelper.percentToSemitones(value) + 12; + } + + @Override + public double valueOf(final int progress) { + return PlayerSemitoneHelper.semitonesToPercent(progress - 12); + } + }; + @Nullable private Callback callback; @State double initialTempo = DEFAULT_TEMPO; @State - double initialPitch = DEFAULT_PITCH; + double initialPitchPercent = DEFAULT_PITCH_PERCENT; @State boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; @State double tempo = DEFAULT_TEMPO; @State - double pitch = DEFAULT_PITCH; + double pitchPercent = DEFAULT_PITCH_PERCENT; @State double stepSize = DEFAULT_STEP; @State @@ -83,11 +100,11 @@ public class PlaybackParameterDialog extends DialogFragment { dialog.callback = callback; dialog.initialTempo = playbackTempo; - dialog.initialPitch = playbackPitch; + dialog.initialPitchPercent = playbackPitch; dialog.initialSkipSilence = playbackSkipSilence; dialog.tempo = dialog.initialTempo; - dialog.pitch = dialog.initialPitch; + dialog.pitchPercent = dialog.initialPitchPercent; dialog.skipSilence = dialog.initialSkipSilence; return dialog; @@ -125,20 +142,19 @@ public class PlaybackParameterDialog extends DialogFragment { binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext())); initUI(); - initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) .setView(binding.getRoot()) .setCancelable(true) .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { setAndUpdateTempo(initialTempo); - setAndUpdatePitch(initialPitch); + setAndUpdatePitch(initialPitchPercent); setAndUpdateSkipSilence(initialSkipSilence); updateCallback(); }) .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> { setAndUpdateTempo(DEFAULT_TEMPO); - setAndUpdatePitch(DEFAULT_PITCH); + setAndUpdatePitch(DEFAULT_PITCH_PERCENT); setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE); updateCallback(); }) @@ -153,12 +169,63 @@ public class PlaybackParameterDialog extends DialogFragment { private void initUI() { // Tempo - setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); - setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); + setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PLAYBACK_VALUE); + setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PLAYBACK_VALUE); - // Pitch - setText(binding.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); - setText(binding.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); + binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + setAndUpdateTempo(tempo); + binding.tempoSeekbar.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener( + QUADRATIC_STRATEGY, + this::onTempoSliderUpdated)); + + registerOnStepClickListener( + binding.tempoStepDown, + () -> tempo, + -1, + this::onTempoSliderUpdated); + registerOnStepClickListener( + binding.tempoStepUp, + () -> tempo, + 1, + this::onTempoSliderUpdated); + + // Pitch - Percent + setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE); + setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE); + + binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + setAndUpdatePitch(pitchPercent); + binding.pitchPercentSeekbar.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener( + QUADRATIC_STRATEGY, + this::onPitchPercentSliderUpdated)); + + registerOnStepClickListener( + binding.pitchPercentStepDown, + () -> pitchPercent, + -1, + this::onPitchPercentSliderUpdated); + registerOnStepClickListener( + binding.pitchPercentStepUp, + () -> pitchPercent, + 1, + this::onPitchPercentSliderUpdated); + + // Pitch - Semitone + binding.pitchSemitoneSeekbar.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener( + SEMITONE_STRATEGY, + this::onPitchPercentSliderUpdated)); + + registerOnSemitoneStepClickListener( + binding.pitchSemitoneStepDown, + -1, + this::onPitchPercentSliderUpdated); + registerOnSemitoneStepClickListener( + binding.pitchSemitoneStepUp, + 1, + this::onPitchPercentSliderUpdated); // Steps setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE); @@ -166,6 +233,34 @@ public class PlaybackParameterDialog extends DialogFragment { setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE); setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); + + setAndUpdateStepSize(stepSize); + + // Bottom controls + bindCheckboxWithBoolPref( + binding.unhookCheckbox, + R.string.playback_unhook_key, + true, + isChecked -> { + if (!isChecked) { + // when unchecked, slide back to the minimum of current tempo or pitch + setSliders(Math.min(pitchPercent, tempo)); + updateCallback(); + } + }); + + setAndUpdateSkipSilence(skipSilence); + binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + skipSilence = isChecked; + updateCallback(); + }); + + bindCheckboxWithBoolPref( + binding.adjustBySemitonesCheckbox, + R.string.playback_adjust_by_semitones_key, + false, + this::showPitchSemitonesOrPercent + ); } private TextView setText( @@ -177,6 +272,31 @@ public class PlaybackParameterDialog extends DialogFragment { return textView; } + private void registerOnStepClickListener( + final TextView stepTextView, + final DoubleSupplier currentValueSupplier, + final double direction, // -1 for step down, +1 for step up + final DoubleConsumer newValueConsumer + ) { + stepTextView.setOnClickListener(view -> { + newValueConsumer.accept( + currentValueSupplier.getAsDouble() + 1 * stepSize * direction); + updateCallback(); + }); + } + + private void registerOnSemitoneStepClickListener( + final TextView stepTextView, + final int direction, // -1 for step down, +1 for step up + final DoubleConsumer newValueConsumer + ) { + stepTextView.setOnClickListener(view -> { + newValueConsumer.accept(PlayerSemitoneHelper.semitonesToPercent( + PlayerSemitoneHelper.percentToSemitones(this.pitchPercent) + direction)); + updateCallback(); + }); + } + private void setupStepTextView( final TextView textView, final double stepSizeValue @@ -185,77 +305,14 @@ public class PlaybackParameterDialog extends DialogFragment { .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); } - private void initUIData() { - // Tempo - binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); - setAndUpdateTempo(tempo); - binding.tempoSeekbar.setOnSeekBarChangeListener( - getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); - - registerOnStepClickListener( - binding.tempoStepDown, tempo, -1, this::onTempoSliderUpdated); - registerOnStepClickListener( - binding.tempoStepUp, tempo, 1, this::onTempoSliderUpdated); - - // Pitch - binding.pitchSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); - setAndUpdatePitch(pitch); - binding.pitchSeekbar.setOnSeekBarChangeListener( - getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); - - registerOnStepClickListener( - binding.pitchStepDown, pitch, -1, this::onPitchSliderUpdated); - registerOnStepClickListener( - binding.pitchStepUp, pitch, 1, this::onPitchSliderUpdated); - - // Steps - setAndUpdateStepSize(stepSize); - - // Bottom controls - // restore whether pitch and tempo are unhooked or not - binding.unhookCheckbox.setChecked(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_unhook_key), true)); - - binding.unhookCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - // save whether pitch and tempo are unhooked or not - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .edit() - .putBoolean(getString(R.string.playback_unhook_key), isChecked) - .apply(); - - if (!isChecked) { - // when unchecked, slide back to the minimum of current tempo or pitch - setSliders(Math.min(pitch, tempo)); - } - }); - - setAndUpdateSkipSilence(skipSilence); - binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - skipSilence = isChecked; - updateCallback(); - }); - } - - private void registerOnStepClickListener( - final TextView stepTextView, - final double currentValue, - final double direction, // -1 for step down, +1 for step up - final DoubleConsumer newValueConsumer - ) { - stepTextView.setOnClickListener(view -> - newValueConsumer.accept(currentValue * direction) - ); - } - private void setAndUpdateStepSize(final double newStepSize) { this.stepSize = newStepSize; binding.tempoStepUp.setText(getStepUpPercentString(newStepSize)); binding.tempoStepDown.setText(getStepDownPercentString(newStepSize)); - binding.pitchStepUp.setText(getStepUpPercentString(newStepSize)); - binding.pitchStepDown.setText(getStepDownPercentString(newStepSize)); + binding.pitchPercentStepUp.setText(getStepUpPercentString(newStepSize)); + binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize)); } private void setAndUpdateSkipSilence(final boolean newSkipSilence) { @@ -263,19 +320,72 @@ public class PlaybackParameterDialog extends DialogFragment { binding.skipSilenceCheckbox.setChecked(newSkipSilence); } + private void bindCheckboxWithBoolPref( + @NonNull final CheckBox checkBox, + @StringRes final int resId, + final boolean defaultValue, + @Nullable final Consumer onInitialValueOrValueChange + ) { + final boolean prefValue = PreferenceManager + .getDefaultSharedPreferences(requireContext()) + .getBoolean(getString(resId), defaultValue); + + checkBox.setChecked(prefValue); + + if (onInitialValueOrValueChange != null) { + onInitialValueOrValueChange.accept(prefValue); + } + + checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + // save whether pitch and tempo are unhooked or not + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .edit() + .putBoolean(getString(resId), isChecked) + .apply(); + + if (onInitialValueOrValueChange != null) { + onInitialValueOrValueChange.accept(isChecked); + } + }); + } + + 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 //////////////////////////////////////////////////////////////////////////*/ private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener( + final SliderStrategy sliderStrategy, final DoubleConsumer newValueConsumer ) { return new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { - if (fromUser) { // this change is first in chain - newValueConsumer.accept(QUADRATIC_STRATEGY.valueOf(progress)); + if (fromUser) { // ensure that the user triggered the change + newValueConsumer.accept(sliderStrategy.valueOf(progress)); updateCallback(); } } @@ -300,7 +410,7 @@ public class PlaybackParameterDialog extends DialogFragment { } } - private void onPitchSliderUpdated(final double newPitch) { + private void onPitchPercentSliderUpdated(final double newPitch) { if (!binding.unhookCheckbox.isChecked()) { setSliders(newPitch); } else { @@ -314,15 +424,39 @@ public class PlaybackParameterDialog extends DialogFragment { } private void setAndUpdateTempo(final double newTempo) { - this.tempo = newTempo; + this.tempo = calcValidTempo(newTempo); + binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo); } private void setAndUpdatePitch(final double newPitch) { - this.pitch = newPitch; - binding.pitchSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); - setText(binding.pitchCurrentText, PlayerHelper::formatPitch, pitch); + this.pitchPercent = calcValidPitch(newPitch); + + binding.pitchPercentSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitchPercent)); + binding.pitchSemitoneSeekbar.setProgress(SEMITONE_STRATEGY.progressOf(pitchPercent)); + setText(binding.pitchPercentCurrentText, + PlayerHelper::formatPitch, + pitchPercent); + setText(binding.pitchSemitoneCurrentText, + PlayerSemitoneHelper::formatPitchSemitones, + pitchPercent); + } + + private double calcValidTempo(final double newTempo) { + return Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newTempo)); + } + + private double calcValidPitch(final double newPitch) { + final double calcPitch = + Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch)); + + if (!binding.adjustBySemitonesCheckbox.isChecked()) { + return calcPitch; + } + + return PlayerSemitoneHelper.semitonesToPercent( + PlayerSemitoneHelper.percentToSemitones(calcPitch)); } /*////////////////////////////////////////////////////////////////////////// @@ -335,12 +469,12 @@ public class PlaybackParameterDialog extends DialogFragment { } if (DEBUG) { Log.d(TAG, "Updating callback: " - + "tempo = [" + tempo + "], " - + "pitch = [" + pitch + "], " - + "skipSilence = [" + skipSilence + "]" + + "tempo = " + tempo + ", " + + "pitchPercent = " + pitchPercent + ", " + + "skipSilence = " + skipSilence ); } - callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence); + callback.onPlaybackParameterChanged((float) tempo, (float) pitchPercent, skipSilence); } @NonNull diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java new file mode 100644 index 000000000..abbcc2c82 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java @@ -0,0 +1,37 @@ +package org.schabi.newpipe.player.helper; + +/** + * Converts between percent and 12-tone equal temperament semitones. + *
+ * @see + * + * Wikipedia: Equal temperament#Twelve-tone equal temperament + * + */ +public final class PlayerSemitoneHelper { + public static final int TONES = 12; + + private PlayerSemitoneHelper() { + // No impl + } + + public static String formatPitchSemitones(final double percent) { + return formatPitchSemitones(percentToSemitones(percent)); + } + + public static String formatPitchSemitones(final int semitones) { + return semitones > 0 ? "+" + semitones : "" + semitones; + } + + public static double semitonesToPercent(final int semitones) { + return Math.pow(2, ensureSemitonesInRange(semitones) / (double) TONES); + } + + public static int percentToSemitones(final double percent) { + return ensureSemitonesInRange((int) Math.round(TONES * Math.log(percent) / Math.log(2))); + } + + private static int ensureSemitonesInRange(final int semitones) { + return Math.max(-TONES, Math.min(TONES, semitones)); + } +} From 321cf8bf7d7a419913f58697cb4cb7e5630212d7 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 21:33:21 +0100 Subject: [PATCH 09/18] Fine tuned dialog --- .../res/layout/dialog_playback_parameter.xml | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 75269ffd3..640475f39 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -1,5 +1,6 @@ + android:orientation="horizontal" + tools:visibility="gone"> + tools:text="0" + tools:ignore="RelativeOverlap" /> + android:textColor="?attr/colorAccent" + tools:text="1%" /> + android:textColor="?attr/colorAccent" + tools:text="5%" /> + android:textColor="?attr/colorAccent" + tools:text="10%" /> + android:textColor="?attr/colorAccent" + tools:text="25%" /> - + android:textColor="?attr/colorAccent" + tools:text="100%" /> Date: Fri, 4 Mar 2022 21:34:45 +0100 Subject: [PATCH 10/18] Code improvements regarding stepSize --- .../helper/PlaybackParameterDialog.java | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 709216ece..eab64e483 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -84,8 +84,6 @@ public class PlaybackParameterDialog extends DialogFragment { @State double pitchPercent = DEFAULT_PITCH_PERCENT; @State - double stepSize = DEFAULT_STEP; - @State boolean skipSilence = DEFAULT_SKIP_SILENCE; private DialogPlaybackParameterBinding binding; @@ -228,13 +226,10 @@ public class PlaybackParameterDialog extends DialogFragment { this::onPitchPercentSliderUpdated); // Steps - setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE); - setupStepTextView(binding.stepSizeFivePercent, STEP_5_PERCENT_VALUE); - setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE); - setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); - setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); - - setAndUpdateStepSize(stepSize); + getStepSizeComponentMappings() + .forEach(this::setupStepTextView); + // Initialize UI + setStepSizeToUI(getCurrentStepSize()); // Bottom controls bindCheckboxWithBoolPref( @@ -263,13 +258,12 @@ public class PlaybackParameterDialog extends DialogFragment { ); } - private TextView setText( + private void setText( final TextView textView, final DoubleFunction formatter, final double value ) { Objects.requireNonNull(textView).setText(formatter.apply(value)); - return textView; } private void registerOnStepClickListener( @@ -280,7 +274,7 @@ public class PlaybackParameterDialog extends DialogFragment { ) { stepTextView.setOnClickListener(view -> { newValueConsumer.accept( - currentValueSupplier.getAsDouble() + 1 * stepSize * direction); + currentValueSupplier.getAsDouble() + 1 * getCurrentStepSize() * direction); updateCallback(); }); } @@ -315,16 +309,22 @@ public class PlaybackParameterDialog extends DialogFragment { binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize)); } + private double getCurrentStepSize() { + return PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP); + } + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { this.skipSilence = newSkipSilence; binding.skipSilenceCheckbox.setChecked(newSkipSilence); } + @SuppressWarnings("SameParameterValue") // this method was written to be reusable private void bindCheckboxWithBoolPref( @NonNull final CheckBox checkBox, @StringRes final int resId, final boolean defaultValue, - @Nullable final Consumer onInitialValueOrValueChange + @NonNull final Consumer onInitialValueOrValueChange ) { final boolean prefValue = PreferenceManager .getDefaultSharedPreferences(requireContext()) @@ -332,9 +332,7 @@ public class PlaybackParameterDialog extends DialogFragment { checkBox.setChecked(prefValue); - if (onInitialValueOrValueChange != null) { - onInitialValueOrValueChange.accept(prefValue); - } + onInitialValueOrValueChange.accept(prefValue); checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> { // save whether pitch and tempo are unhooked or not @@ -343,9 +341,7 @@ public class PlaybackParameterDialog extends DialogFragment { .putBoolean(getString(resId), isChecked) .apply(); - if (onInitialValueOrValueChange != null) { - onInitialValueOrValueChange.accept(isChecked); - } + onInitialValueOrValueChange.accept(isChecked); }); } From 4b0653658273f456ddbc56a86b1695073d982895 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 21:37:11 +0100 Subject: [PATCH 11/18] Reworked switching to semitones Using an expandable Tab-like component instead of a combobox --- .../schabi/newpipe/local/feed/FeedFragment.kt | 16 +- .../helper/PlaybackParameterDialog.java | 169 ++++++++++++++---- .../schabi/newpipe/util/DrawableResolver.kt | 26 +++ .../res/layout/dialog_playback_parameter.xml | 59 ++++-- app/src/main/res/values/strings.xml | 2 + 5 files changed, 209 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index e97629f31..e8e78feda 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -25,7 +25,6 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.graphics.Typeface -import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.os.Parcelable @@ -37,7 +36,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.Button -import androidx.annotation.AttrRes import androidx.annotation.Nullable import androidx.appcompat.app.AlertDialog 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.subscription.SubscriptionManager 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.NavigationHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams @@ -579,19 +578,6 @@ class FeedFragment : BaseStateFragment() { 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() { tryGetNewItemsLoadedButton()?.clearAnimation() tryGetNewItemsLoadedButton() diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index eab64e483..902222cc5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -1,10 +1,14 @@ 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.util.DrawableResolver.resolveDrawable; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import android.app.Dialog; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -22,8 +26,11 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.SliderStrategy; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.function.Consumer; 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 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_5_PERCENT_VALUE = 0.05f; private static final double STEP_10_PERCENT_VALUE = 0.10f; @@ -188,6 +198,22 @@ public class PlaybackParameterDialog extends DialogFragment { 1, 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 setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE); setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE); @@ -249,13 +275,6 @@ public class PlaybackParameterDialog extends DialogFragment { skipSilence = isChecked; updateCallback(); }); - - bindCheckboxWithBoolPref( - binding.adjustBySemitonesCheckbox, - R.string.playback_adjust_by_semitones_key, - false, - this::showPitchSemitonesOrPercent - ); } private void setText( @@ -291,17 +310,114 @@ public class PlaybackParameterDialog extends DialogFragment { }); } - private void setupStepTextView( - final TextView textView, - final double stepSizeValue + private void setupPitchControlModeTextView( + final boolean semitones, + final TextView textView ) { - setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue) - .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); + textView.setOnClickListener(view -> { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .edit() + .putBoolean(getString(R.string.playback_adjust_by_semitones_key), semitones) + .apply(); + + changePitchControlMode(semitones); + }); } - private void setAndUpdateStepSize(final double newStepSize) { - this.stepSize = newStepSize; + private Map getPitchControlModeComponentMappings() { + final Map 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 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 getStepSizeComponentMappings() { + final Map 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 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.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 //////////////////////////////////////////////////////////////////////////*/ @@ -447,7 +540,7 @@ public class PlaybackParameterDialog extends DialogFragment { final double calcPitch = Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch)); - if (!binding.adjustBySemitonesCheckbox.isChecked()) { + if (!isCurrentPitchControlModeSemitone()) { return calcPitch; } diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt new file mode 100644 index 000000000..50f875257 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt @@ -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 + ) + } + } +} diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 640475f39..e402f4fb1 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -146,11 +146,59 @@ android:textColor="?attr/colorAccent" android:textStyle="bold" /> + + + + + + + + + + - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 792e6414b..af2921cca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -501,6 +501,8 @@ Step Tempo step Reset + Percent + Semitone 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. From 20602889be38e38c25e6022f56be83860ef34313 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 22:02:39 +0100 Subject: [PATCH 12/18] Added some doc and abstracted more methods --- .../helper/PlaybackParameterDialog.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 902222cc5..4ab8f9248 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -172,7 +172,7 @@ public class PlaybackParameterDialog extends DialogFragment { } /*////////////////////////////////////////////////////////////////////////// - // Control Views + // UI Initialization and Control //////////////////////////////////////////////////////////////////////////*/ private void initUI() { @@ -265,8 +265,7 @@ public class PlaybackParameterDialog extends DialogFragment { isChecked -> { if (!isChecked) { // when unchecked, slide back to the minimum of current tempo or pitch - setSliders(Math.min(pitchPercent, tempo)); - updateCallback(); + ensureHookIsValidAndUpdateCallBack(); } }); @@ -277,6 +276,8 @@ public class PlaybackParameterDialog extends DialogFragment { }); } + // -- General formatting -- + private void setText( final TextView textView, final DoubleFunction formatter, @@ -285,6 +286,8 @@ public class PlaybackParameterDialog extends DialogFragment { Objects.requireNonNull(textView).setText(formatter.apply(value)); } + // -- Steps -- + private void registerOnStepClickListener( final TextView stepTextView, final DoubleSupplier currentValueSupplier, @@ -310,6 +313,8 @@ public class PlaybackParameterDialog extends DialogFragment { }); } + // -- Pitch -- + private void setupPitchControlModeTextView( final boolean semitones, final TextView textView @@ -367,6 +372,9 @@ public class PlaybackParameterDialog extends DialogFragment { this.onPitchPercentSliderUpdated(newPitchPercent); updateCallback(); } + } else if (!binding.unhookCheckbox.isChecked()) { + // When changing to percent it's possible that tempo is != pitch + ensureHookIsValidAndUpdateCallBack(); } } @@ -377,6 +385,8 @@ public class PlaybackParameterDialog extends DialogFragment { PITCH_CTRL_MODE_PERCENT); } + // -- Steps (Set) -- + private void setupStepTextView( final double stepSizeValue, final TextView textView @@ -430,6 +440,8 @@ public class PlaybackParameterDialog extends DialogFragment { .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP); } + // -- Additional options -- + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { this.skipSilence = newSkipSilence; binding.skipSilenceCheckbox.setChecked(newSkipSilence); @@ -461,6 +473,18 @@ public class PlaybackParameterDialog extends DialogFragment { }); } + /** + * Ensures that the slider hook is valid and if not sets and updates the sliders accordingly. + *
+ * You have to ensure by yourself that the hooking is active. + */ + private void ensureHookIsValidAndUpdateCallBack() { + if (tempo != pitchPercent) { + setSliders(Math.min(tempo, pitchPercent)); + updateCallback(); + } + } + /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ From 1b8c517e3ea1924787eeff075a575414a5f3b544 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 22:21:17 +0100 Subject: [PATCH 13/18] Removed unused strings --- app/src/main/res/values/strings.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index af2921cca..1f87ae9fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -497,9 +497,7 @@ Pitch Unhook (may cause distortion) Fast-forward during silence - Adjust pitch by musical semitones Step - Tempo step Reset Percent Semitone From 44dada9e60b23f500c3904af8578a874e78c0c5a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 6 Mar 2022 16:10:42 +0100 Subject: [PATCH 14/18] Use better Kotlin syntax From the PR review --- .../schabi/newpipe/local/feed/FeedFragment.kt | 2 +- .../schabi/newpipe/util/DrawableResolver.kt | 29 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index e8e78feda..55810284f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -75,7 +75,7 @@ import org.schabi.newpipe.local.feed.item.StreamItem import org.schabi.newpipe.local.feed.service.FeedLoadService import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.DeviceUtils -import org.schabi.newpipe.util.DrawableResolver.Companion.resolveDrawable +import org.schabi.newpipe.util.DrawableResolver.resolveDrawable import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt index 50f875257..ccc9e7dd4 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt +++ b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt @@ -2,25 +2,24 @@ package org.schabi.newpipe.util import android.content.Context import android.graphics.drawable.Drawable +import android.util.TypedValue 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 - ) - } +object DrawableResolver { + @JvmStatic + fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { + return androidx.core.content.ContextCompat.getDrawable( + context, + TypedValue().apply { + context.theme.resolveAttribute( + attrResId, + this, + true + ) + }.resourceId + ) } } From b9190eddfe1563cc5269caf2ca90a997db85a920 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 7 Mar 2022 20:30:25 +0100 Subject: [PATCH 15/18] Update DrawableResolver.kt Nicer import :wink: --- app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt index ccc9e7dd4..8a728bfbf 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt +++ b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.drawable.Drawable import android.util.TypedValue import androidx.annotation.AttrRes +import androidx.core.content.ContextCompat /** * Utility class for resolving [Drawables](Drawable) @@ -11,7 +12,7 @@ import androidx.annotation.AttrRes object DrawableResolver { @JvmStatic fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return androidx.core.content.ContextCompat.getDrawable( + return ContextCompat.getDrawable( context, TypedValue().apply { context.theme.resolveAttribute( From 0f551baf3729121dadc0597d615aab58ea13e941 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 16 Mar 2022 14:53:33 +0100 Subject: [PATCH 16/18] Refactored code --- .../helper/PlaybackParameterDialog.java | 24 +++++++++---------- .../player/helper/PlayerSemitoneHelper.java | 9 +++---- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 4ab8f9248..26caa1b20 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -44,8 +44,8 @@ public class PlaybackParameterDialog extends DialogFragment { private static final String TAG = "PlaybackParameterDialog"; // Minimum allowable range in ExoPlayer - private static final double MIN_PLAYBACK_VALUE = 0.10f; - private static final double MAX_PLAYBACK_VALUE = 3.00f; + private static final double MIN_PITCH_OR_SPEED = 0.10f; + private static final double MAX_PITCH_OR_SPEED = 3.00f; private static final boolean PITCH_CTRL_MODE_PERCENT = false; private static final boolean PITCH_CTRL_MODE_SEMITONE = true; @@ -62,8 +62,8 @@ public class PlaybackParameterDialog extends DialogFragment { private static final boolean DEFAULT_SKIP_SILENCE = false; private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( - MIN_PLAYBACK_VALUE, - MAX_PLAYBACK_VALUE, + MIN_PITCH_OR_SPEED, + MAX_PITCH_OR_SPEED, 1.00f, 10_000); @@ -177,10 +177,10 @@ public class PlaybackParameterDialog extends DialogFragment { private void initUI() { // Tempo - setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PLAYBACK_VALUE); - setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PLAYBACK_VALUE); + setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PITCH_OR_SPEED); + setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PITCH_OR_SPEED); - binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED)); setAndUpdateTempo(tempo); binding.tempoSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener( @@ -215,10 +215,10 @@ public class PlaybackParameterDialog extends DialogFragment { changePitchControlMode(isCurrentPitchControlModeSemitone()); // Pitch - Percent - setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE); - setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE); + setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PITCH_OR_SPEED); + setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PITCH_OR_SPEED); - binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED)); setAndUpdatePitch(pitchPercent); binding.pitchPercentSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener( @@ -557,12 +557,12 @@ public class PlaybackParameterDialog extends DialogFragment { } private double calcValidTempo(final double newTempo) { - return Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newTempo)); + return Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newTempo)); } private double calcValidPitch(final double newPitch) { final double calcPitch = - Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch)); + Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newPitch)); if (!isCurrentPitchControlModeSemitone()) { return calcPitch; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java index abbcc2c82..f3a71d7cd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java @@ -9,7 +9,7 @@ package org.schabi.newpipe.player.helper; * */ public final class PlayerSemitoneHelper { - public static final int TONES = 12; + public static final int SEMITONE_COUNT = 12; private PlayerSemitoneHelper() { // No impl @@ -24,14 +24,15 @@ public final class PlayerSemitoneHelper { } public static double semitonesToPercent(final int semitones) { - return Math.pow(2, ensureSemitonesInRange(semitones) / (double) TONES); + return Math.pow(2, ensureSemitonesInRange(semitones) / (double) SEMITONE_COUNT); } public static int percentToSemitones(final double percent) { - return ensureSemitonesInRange((int) Math.round(TONES * Math.log(percent) / Math.log(2))); + return ensureSemitonesInRange( + (int) Math.round(SEMITONE_COUNT * Math.log(percent) / Math.log(2))); } private static int ensureSemitonesInRange(final int semitones) { - return Math.max(-TONES, Math.min(TONES, semitones)); + return Math.max(-SEMITONE_COUNT, Math.min(SEMITONE_COUNT, semitones)); } } From 1dc146322c5666f2a4af9f9d14b5f2aa56626b32 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:34:44 +0100 Subject: [PATCH 17/18] Merged ``DrawableResolver`` into ``ThemeHelper`` --- .../schabi/newpipe/local/feed/FeedFragment.kt | 2 +- .../helper/PlaybackParameterDialog.java | 2 +- .../schabi/newpipe/util/DrawableResolver.kt | 26 ------------------- .../org/schabi/newpipe/util/ThemeHelper.java | 18 +++++++++++++ 4 files changed, 20 insertions(+), 28 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 55810284f..b291aa035 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -75,10 +75,10 @@ import org.schabi.newpipe.local.feed.item.StreamItem import org.schabi.newpipe.local.feed.service.FeedLoadService import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.DeviceUtils -import org.schabi.newpipe.util.DrawableResolver.resolveDrawable import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams +import org.schabi.newpipe.util.ThemeHelper.resolveDrawable import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout import java.time.OffsetDateTime import java.util.function.Consumer diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 26caa1b20..62446b50e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -2,8 +2,8 @@ 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.util.DrawableResolver.resolveDrawable; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; +import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable; import android.app.Dialog; import android.content.Context; diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt deleted file mode 100644 index 8a728bfbf..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.schabi.newpipe.util - -import android.content.Context -import android.graphics.drawable.Drawable -import android.util.TypedValue -import androidx.annotation.AttrRes -import androidx.core.content.ContextCompat - -/** - * Utility class for resolving [Drawables](Drawable) - */ -object DrawableResolver { - @JvmStatic - fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return ContextCompat.getDrawable( - context, - TypedValue().apply { - context.theme.resolveAttribute( - attrResId, - this, - true - ) - }.resourceId - ) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index 7c47d387f..7d06e57b6 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -23,9 +23,11 @@ import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.util.TypedValue; import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.appcompat.app.ActionBar; @@ -227,6 +229,22 @@ public final class ThemeHelper { return value.data; } + /** + * Resolves a {@link Drawable} by it's id. + * + * @param context Context + * @param attrResId Resource id + * @return the {@link Drawable} + */ + public static Drawable resolveDrawable( + @NonNull final Context context, + @AttrRes final int attrResId + ) { + final TypedValue typedValue = new TypedValue(); + context.getTheme().resolveAttribute(attrResId, typedValue, true); + return ContextCompat.getDrawable(context, typedValue.resourceId); + } + private static String getSelectedThemeKey(final Context context) { final String themeKey = context.getString(R.string.theme_key); final String defaultTheme = context.getResources().getString(R.string.default_theme_value); From a311519314085d5e79e1a55afc7c16dc744cb57b Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sat, 16 Apr 2022 21:24:01 +0200 Subject: [PATCH 18/18] Fix merge conflicts --- .../player/helper/PlaybackParameterDialog.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 62446b50e..2d1461aaf 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -27,6 +27,7 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; import java.util.HashMap; @@ -37,6 +38,8 @@ import java.util.function.DoubleConsumer; import java.util.function.DoubleFunction; import java.util.function.DoubleSupplier; +import javax.annotation.Nonnull; + import icepick.Icepick; import icepick.State; @@ -493,25 +496,16 @@ public class PlaybackParameterDialog extends DialogFragment { final SliderStrategy sliderStrategy, final DoubleConsumer newValueConsumer ) { - return new SeekBar.OnSeekBarChangeListener() { + return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(final SeekBar seekBar, final int progress, + public void onProgressChanged(@Nonnull final SeekBar seekBar, + final int progress, final boolean fromUser) { if (fromUser) { // ensure that the user triggered the change newValueConsumer.accept(sliderStrategy.valueOf(progress)); updateCallback(); } } - - @Override - public void onStartTrackingTouch(final SeekBar seekBar) { - // Do nothing - } - - @Override - public void onStopTrackingTouch(final SeekBar seekBar) { - // Do nothing - } }; }