Skip to content

Commit 4b06536

Browse files
committed
Reworked switching to semitones
Using an expandable Tab-like component instead of a combobox
1 parent 621b38c commit 4b06536

5 files changed

Lines changed: 208 additions & 62 deletions

File tree

app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import android.content.Context
2525
import android.content.Intent
2626
import android.content.SharedPreferences
2727
import android.graphics.Typeface
28-
import android.graphics.drawable.Drawable
2928
import android.graphics.drawable.LayerDrawable
3029
import android.os.Bundle
3130
import android.os.Parcelable
@@ -37,7 +36,6 @@ import android.view.MenuItem
3736
import android.view.View
3837
import android.view.ViewGroup
3938
import android.widget.Button
40-
import androidx.annotation.AttrRes
4139
import androidx.annotation.Nullable
4240
import androidx.appcompat.app.AlertDialog
4341
import androidx.appcompat.content.res.AppCompatResources
@@ -77,6 +75,7 @@ import org.schabi.newpipe.local.feed.item.StreamItem
7775
import org.schabi.newpipe.local.feed.service.FeedLoadService
7876
import org.schabi.newpipe.local.subscription.SubscriptionManager
7977
import org.schabi.newpipe.util.DeviceUtils
78+
import org.schabi.newpipe.util.DrawableResolver.Companion.resolveDrawable
8079
import org.schabi.newpipe.util.Localization
8180
import org.schabi.newpipe.util.NavigationHelper
8281
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams
@@ -579,19 +578,6 @@ class FeedFragment : BaseStateFragment<FeedState>() {
579578
lastNewItemsCount = highlightCount
580579
}
581580

582-
private fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? {
583-
return androidx.core.content.ContextCompat.getDrawable(
584-
context,
585-
android.util.TypedValue().apply {
586-
context.theme.resolveAttribute(
587-
attrResId,
588-
this,
589-
true
590-
)
591-
}.resourceId
592-
)
593-
}
594-
595581
private fun showNewItemsLoaded() {
596582
tryGetNewItemsLoadedButton()?.clearAnimation()
597583
tryGetNewItemsLoadedButton()

app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java

Lines changed: 130 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package org.schabi.newpipe.player.helper;
22

3+
import static org.schabi.newpipe.ktx.ViewUtils.animateRotation;
34
import static org.schabi.newpipe.player.Player.DEBUG;
5+
import static org.schabi.newpipe.util.DrawableResolver.resolveDrawable;
46
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
57

68
import android.app.Dialog;
79
import android.content.Context;
10+
import android.graphics.drawable.Drawable;
11+
import android.graphics.drawable.LayerDrawable;
812
import android.os.Bundle;
913
import android.util.Log;
1014
import android.view.LayoutInflater;
@@ -22,8 +26,11 @@
2226

2327
import org.schabi.newpipe.R;
2428
import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding;
29+
import org.schabi.newpipe.player.Player;
2530
import org.schabi.newpipe.util.SliderStrategy;
2631

32+
import java.util.HashMap;
33+
import java.util.Map;
2734
import java.util.Objects;
2835
import java.util.function.Consumer;
2936
import java.util.function.DoubleConsumer;
@@ -40,6 +47,9 @@ public class PlaybackParameterDialog extends DialogFragment {
4047
private static final double MIN_PLAYBACK_VALUE = 0.10f;
4148
private static final double MAX_PLAYBACK_VALUE = 3.00f;
4249

50+
private static final boolean PITCH_CTRL_MODE_PERCENT = false;
51+
private static final boolean PITCH_CTRL_MODE_SEMITONE = true;
52+
4353
private static final double STEP_1_PERCENT_VALUE = 0.01f;
4454
private static final double STEP_5_PERCENT_VALUE = 0.05f;
4555
private static final double STEP_10_PERCENT_VALUE = 0.10f;
@@ -188,6 +198,22 @@ private void initUI() {
188198
1,
189199
this::onTempoSliderUpdated);
190200

201+
// Pitch
202+
binding.pitchToogleControlModes.setOnClickListener(v -> {
203+
final boolean isCurrentlyVisible =
204+
binding.pitchControlModeTabs.getVisibility() == View.GONE;
205+
binding.pitchControlModeTabs.setVisibility(isCurrentlyVisible
206+
? View.VISIBLE
207+
: View.GONE);
208+
animateRotation(binding.pitchToogleControlModes,
209+
Player.DEFAULT_CONTROLS_DURATION,
210+
isCurrentlyVisible ? 180 : 0);
211+
});
212+
213+
getPitchControlModeComponentMappings()
214+
.forEach(this::setupPitchControlModeTextView);
215+
changePitchControlMode(isCurrentPitchControlModeSemitone());
216+
191217
// Pitch - Percent
192218
setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE);
193219
setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE);
@@ -249,13 +275,6 @@ private void initUI() {
249275
skipSilence = isChecked;
250276
updateCallback();
251277
});
252-
253-
bindCheckboxWithBoolPref(
254-
binding.adjustBySemitonesCheckbox,
255-
R.string.playback_adjust_by_semitones_key,
256-
false,
257-
this::showPitchSemitonesOrPercent
258-
);
259278
}
260279

261280
private void setText(
@@ -291,17 +310,114 @@ private void registerOnSemitoneStepClickListener(
291310
});
292311
}
293312

313+
private void setupPitchControlModeTextView(
314+
final boolean semitones,
315+
final TextView textView
316+
) {
317+
textView.setOnClickListener(view -> {
318+
PreferenceManager.getDefaultSharedPreferences(requireContext())
319+
.edit()
320+
.putBoolean(getString(R.string.playback_adjust_by_semitones_key), semitones)
321+
.apply();
322+
323+
changePitchControlMode(semitones);
324+
});
325+
}
326+
327+
private Map<Boolean, TextView> getPitchControlModeComponentMappings() {
328+
final Map<Boolean, TextView> mappings = new HashMap<>();
329+
mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent);
330+
mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone);
331+
return mappings;
332+
}
333+
334+
private void changePitchControlMode(final boolean semitones) {
335+
// Bring all textviews into a normal state
336+
final Map<Boolean, TextView> pitchCtrlModeComponentMapping =
337+
getPitchControlModeComponentMappings();
338+
pitchCtrlModeComponentMapping.forEach((v, textView) -> textView.setBackground(
339+
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
340+
341+
// Mark the selected textview
342+
final TextView textView = pitchCtrlModeComponentMapping.get(semitones);
343+
if (textView != null) {
344+
textView.setBackground(new LayerDrawable(new Drawable[]{
345+
resolveDrawable(requireContext(), R.attr.dashed_border),
346+
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
347+
}));
348+
}
349+
350+
// Show or hide component
351+
binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE);
352+
binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE);
353+
354+
if (semitones) {
355+
// Recalculate pitch percent when changing to semitone
356+
// (as it could be an invalid semitone value)
357+
final double newPitchPercent = calcValidPitch(pitchPercent);
358+
359+
// If the values differ set the new pitch
360+
if (this.pitchPercent != newPitchPercent) {
361+
if (DEBUG) {
362+
Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: "
363+
+ "currentPitchPercent = " + pitchPercent + ", "
364+
+ "newPitchPercent = " + newPitchPercent
365+
);
366+
}
367+
this.onPitchPercentSliderUpdated(newPitchPercent);
368+
updateCallback();
369+
}
370+
}
371+
}
372+
373+
private boolean isCurrentPitchControlModeSemitone() {
374+
return PreferenceManager.getDefaultSharedPreferences(requireContext())
375+
.getBoolean(
376+
getString(R.string.playback_adjust_by_semitones_key),
377+
PITCH_CTRL_MODE_PERCENT);
378+
}
379+
294380
private void setupStepTextView(
295-
final TextView textView,
296-
final double stepSizeValue
381+
final double stepSizeValue,
382+
final TextView textView
297383
) {
298-
setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue)
299-
.setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue));
384+
setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue);
385+
textView.setOnClickListener(view -> {
386+
PreferenceManager.getDefaultSharedPreferences(requireContext())
387+
.edit()
388+
.putFloat(getString(R.string.adjustment_step_key), (float) stepSizeValue)
389+
.apply();
390+
391+
setStepSizeToUI(stepSizeValue);
392+
});
300393
}
301394

302-
private void setAndUpdateStepSize(final double newStepSize) {
303-
this.stepSize = newStepSize;
395+
private Map<Double, TextView> getStepSizeComponentMappings() {
396+
final Map<Double, TextView> mappings = new HashMap<>();
397+
mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent);
398+
mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent);
399+
mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent);
400+
mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent);
401+
mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent);
402+
return mappings;
403+
}
404+
405+
private void setStepSizeToUI(final double newStepSize) {
406+
// Bring all textviews into a normal state
407+
final Map<Double, TextView> stepSiteComponentMapping = getStepSizeComponentMappings();
408+
stepSiteComponentMapping.forEach((v, textView) -> textView.setBackground(
409+
resolveDrawable(requireContext(), R.attr.selectableItemBackground)));
410+
411+
// Mark the selected textview
412+
final TextView textView = stepSiteComponentMapping.get(newStepSize);
413+
if (textView != null) {
414+
textView.setBackground(new LayerDrawable(new Drawable[]{
415+
resolveDrawable(requireContext(), R.attr.dashed_border),
416+
resolveDrawable(requireContext(), R.attr.selectableItemBackground)
417+
}));
418+
}
304419

420+
// Bind to the corresponding control components
305421
binding.tempoStepUp.setText(getStepUpPercentString(newStepSize));
306422
binding.tempoStepDown.setText(getStepDownPercentString(newStepSize));
307423

@@ -345,29 +461,6 @@ private void bindCheckboxWithBoolPref(
345461
});
346462
}
347463

348-
private void showPitchSemitonesOrPercent(final boolean semitones) {
349-
binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE);
350-
binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE);
351-
352-
if (semitones) {
353-
// Recalculate pitch percent when changing to semitone
354-
// (as it could be an invalid semitone value)
355-
final double newPitchPercent = calcValidPitch(pitchPercent);
356-
357-
// If the values differ set the new pitch
358-
if (this.pitchPercent != newPitchPercent) {
359-
if (DEBUG) {
360-
Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: "
361-
+ "currentPitchPercent = " + pitchPercent + ", "
362-
+ "newPitchPercent = " + newPitchPercent
363-
);
364-
}
365-
this.onPitchPercentSliderUpdated(newPitchPercent);
366-
updateCallback();
367-
}
368-
}
369-
}
370-
371464
/*//////////////////////////////////////////////////////////////////////////
372465
// Sliders
373466
//////////////////////////////////////////////////////////////////////////*/
@@ -447,7 +540,7 @@ private double calcValidPitch(final double newPitch) {
447540
final double calcPitch =
448541
Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch));
449542

450-
if (!binding.adjustBySemitonesCheckbox.isChecked()) {
543+
if (!isCurrentPitchControlModeSemitone()) {
451544
return calcPitch;
452545
}
453546

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.schabi.newpipe.util
2+
3+
import android.content.Context
4+
import android.graphics.drawable.Drawable
5+
import androidx.annotation.AttrRes
6+
7+
/**
8+
* Utility class for resolving [Drawables](Drawable)
9+
*/
10+
class DrawableResolver {
11+
companion object {
12+
@JvmStatic
13+
fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? {
14+
return androidx.core.content.ContextCompat.getDrawable(
15+
context,
16+
android.util.TypedValue().apply {
17+
context.theme.resolveAttribute(
18+
attrResId,
19+
this,
20+
true
21+
)
22+
}.resourceId
23+
)
24+
}
25+
}
26+
}

app/src/main/res/layout/dialog_playback_parameter.xml

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,59 @@
146146
android:textColor="?attr/colorAccent"
147147
android:textStyle="bold" />
148148

149+
<ImageView
150+
android:id="@+id/pitchToogleControlModes"
151+
android:layout_width="22dp"
152+
android:layout_height="22dp"
153+
android:layout_below="@id/separatorPitch"
154+
android:layout_alignParentEnd="true"
155+
android:clickable="true"
156+
android:focusable="true"
157+
app:srcCompat="@drawable/ic_expand_more"
158+
tools:ignore="ContentDescription" />
159+
160+
<LinearLayout
161+
android:id="@+id/pitchControlModeTabs"
162+
android:layout_width="match_parent"
163+
android:layout_height="22dp"
164+
android:layout_below="@id/pitchControlText"
165+
android:layout_marginStart="22dp"
166+
android:layout_marginEnd="22dp"
167+
android:orientation="horizontal"
168+
android:visibility="gone"
169+
tools:visibility="visible">
170+
171+
<org.schabi.newpipe.views.NewPipeTextView
172+
android:id="@+id/pitchControlModePercent"
173+
android:layout_width="0dp"
174+
android:layout_height="match_parent"
175+
android:layout_weight="1"
176+
android:background="?attr/selectableItemBackground"
177+
android:clickable="true"
178+
android:focusable="true"
179+
android:gravity="center"
180+
android:text="@string/percent"
181+
android:textColor="?attr/colorAccent" />
182+
183+
<org.schabi.newpipe.views.NewPipeTextView
184+
android:id="@+id/pitchControlModeSemitone"
185+
android:layout_width="0dp"
186+
android:layout_height="match_parent"
187+
android:layout_weight="1"
188+
android:background="?attr/selectableItemBackground"
189+
android:clickable="true"
190+
android:focusable="true"
191+
android:gravity="center"
192+
android:text="@string/semitone"
193+
android:textColor="?attr/colorAccent" />
194+
195+
</LinearLayout>
196+
149197
<RelativeLayout
150198
android:id="@+id/pitchControlContainer"
151199
android:layout_width="match_parent"
152200
android:layout_height="wrap_content"
153-
android:layout_below="@id/pitchControlText"
201+
android:layout_below="@id/pitchControlModeTabs"
154202
android:layout_marginTop="1dp">
155203

156204
<RelativeLayout
@@ -471,15 +519,6 @@
471519
android:focusable="true"
472520
android:text="@string/skip_silence_checkbox" />
473521

474-
<CheckBox
475-
android:id="@+id/adjustBySemitonesCheckbox"
476-
android:layout_width="match_parent"
477-
android:layout_height="wrap_content"
478-
android:checked="false"
479-
android:clickable="true"
480-
android:focusable="true"
481-
android:maxLines="1"
482-
android:text="@string/adjust_by_semitones_checkbox" />
483522
</LinearLayout>
484523

485524
<!-- END HERE -->

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,8 @@
501501
<string name="playback_step">Step</string>
502502
<string name="playback_tempo_step">Tempo step</string>
503503
<string name="playback_reset">Reset</string>
504+
<string name="percent">Percent</string>
505+
<string name="semitone">Semitone</string>
504506
<!-- GDPR dialog -->
505507
<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.
506508
\nYou must accept it to send us the bug report.</string>

0 commit comments

Comments
 (0)