Reworked switching to semitones

Using an expandable Tab-like component instead of a combobox
This commit is contained in:
litetex 2022-03-04 21:37:11 +01:00
parent 621b38c98b
commit 4b06536582
5 changed files with 209 additions and 63 deletions

View File

@ -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<FeedState>() {
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()

View File

@ -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<Boolean, TextView> getPitchControlModeComponentMappings() {
final Map<Boolean, TextView> mappings = new HashMap<>();
mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent);
mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone);
return mappings;
}
private void changePitchControlMode(final boolean semitones) {
// Bring all textviews into a normal state
final Map<Boolean, TextView> pitchCtrlModeComponentMapping =
getPitchControlModeComponentMappings();
pitchCtrlModeComponentMapping.forEach((v, textView) -> textView.setBackground(
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
// Mark the selected textview
final TextView textView = pitchCtrlModeComponentMapping.get(semitones);
if (textView != null) {
textView.setBackground(new LayerDrawable(new Drawable[]{
resolveDrawable(requireContext(), R.attr.dashed_border),
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
}));
}
// Show or hide component
binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE);
binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE);
if (semitones) {
// Recalculate pitch percent when changing to semitone
// (as it could be an invalid semitone value)
final double newPitchPercent = calcValidPitch(pitchPercent);
// If the values differ set the new pitch
if (this.pitchPercent != newPitchPercent) {
if (DEBUG) {
Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: "
+ "currentPitchPercent = " + pitchPercent + ", "
+ "newPitchPercent = " + newPitchPercent
);
}
this.onPitchPercentSliderUpdated(newPitchPercent);
updateCallback();
}
}
}
private boolean isCurrentPitchControlModeSemitone() {
return PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(
getString(R.string.playback_adjust_by_semitones_key),
PITCH_CTRL_MODE_PERCENT);
}
private void setupStepTextView(
final double stepSizeValue,
final TextView textView
) {
setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue);
textView.setOnClickListener(view -> {
PreferenceManager.getDefaultSharedPreferences(requireContext())
.edit()
.putFloat(getString(R.string.adjustment_step_key), (float) stepSizeValue)
.apply();
setStepSizeToUI(stepSizeValue);
});
}
private Map<Double, TextView> getStepSizeComponentMappings() {
final Map<Double, TextView> mappings = new HashMap<>();
mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent);
mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent);
mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent);
mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent);
mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent);
return mappings;
}
private void setStepSizeToUI(final double newStepSize) {
// Bring all textviews into a normal state
final Map<Double, TextView> stepSiteComponentMapping = getStepSizeComponentMappings();
stepSiteComponentMapping.forEach((v, textView) -> textView.setBackground(
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
// Mark the selected textview
final TextView textView = stepSiteComponentMapping.get(newStepSize);
if (textView != null) {
textView.setBackground(new LayerDrawable(new Drawable[]{
resolveDrawable(requireContext(), R.attr.dashed_border),
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
}));
}
// Bind to the corresponding control components
binding.tempoStepUp.setText(getStepUpPercentString(newStepSize));
binding.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;
}

View File

@ -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
)
}
}
}

View File

@ -146,11 +146,59 @@
android:textColor="?attr/colorAccent"
android:textStyle="bold" />
<ImageView
android:id="@+id/pitchToogleControlModes"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_below="@id/separatorPitch"
android:layout_alignParentEnd="true"
android:clickable="true"
android:focusable="true"
app:srcCompat="@drawable/ic_expand_more"
tools:ignore="ContentDescription" />
<LinearLayout
android:id="@+id/pitchControlModeTabs"
android:layout_width="match_parent"
android:layout_height="22dp"
android:layout_below="@id/pitchControlText"
android:layout_marginStart="22dp"
android:layout_marginEnd="22dp"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/pitchControlModePercent"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:text="@string/percent"
android:textColor="?attr/colorAccent" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/pitchControlModeSemitone"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:text="@string/semitone"
android:textColor="?attr/colorAccent" />
</LinearLayout>
<RelativeLayout
android:id="@+id/pitchControlContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/pitchControlText"
android:layout_below="@id/pitchControlModeTabs"
android:layout_marginTop="1dp">
<RelativeLayout
@ -471,15 +519,6 @@
android:focusable="true"
android:text="@string/skip_silence_checkbox" />
<CheckBox
android:id="@+id/adjustBySemitonesCheckbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:clickable="true"
android:focusable="true"
android:maxLines="1"
android:text="@string/adjust_by_semitones_checkbox" />
</LinearLayout>
<!-- END HERE -->

View File

@ -501,6 +501,8 @@
<string name="playback_step">Step</string>
<string name="playback_tempo_step">Tempo step</string>
<string name="playback_reset">Reset</string>
<string name="percent">Percent</string>
<string name="semitone">Semitone</string>
<!-- GDPR dialog -->
<string name="start_accept_privacy_policy">In order to comply with the European General Data Protection Regulation (GDPR), we hereby draw your attention to NewPipe\'s privacy policy. Please read it carefully.
\nYou must accept it to send us the bug report.</string>