Skip to content

Commit

Permalink
Fix download dialog selector layout and add some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mauriciocolli committed Dec 8, 2021
1 parent 4825a0a commit 363c37a
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package org.schabi.newpipe.util

import android.content.Context
import android.util.SparseArray
import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.Spinner
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.MediaFormat
import org.schabi.newpipe.extractor.stream.AudioStream
import org.schabi.newpipe.extractor.stream.Stream
import org.schabi.newpipe.extractor.stream.SubtitlesStream
import org.schabi.newpipe.extractor.stream.VideoStream

@MediumTest
@RunWith(AndroidJUnit4::class)
class StreamItemAdapterTest {
private lateinit var context: Context
private lateinit var spinner: Spinner

@Before
fun setUp() {
context = ApplicationProvider.getApplicationContext()
UiThreadStatement.runOnUiThread {
spinner = Spinner(context)
}
}

@Test
fun videoStreams_noSecondaryStream() {
val adapter = StreamItemAdapter<VideoStream, AudioStream>(
context,
getVideoStreams(true, true, true, true),
null
)

spinner.adapter = adapter
assertIconVisibility(spinner, 0, VISIBLE, VISIBLE)
assertIconVisibility(spinner, 1, VISIBLE, VISIBLE)
assertIconVisibility(spinner, 2, VISIBLE, VISIBLE)
assertIconVisibility(spinner, 3, VISIBLE, VISIBLE)
}

@Test
fun videoStreams_hasSecondaryStream() {
val adapter = StreamItemAdapter(
context,
getVideoStreams(false, true, false, true),
getAudioStreams(false, true, false, true)
)

spinner.adapter = adapter
assertIconVisibility(spinner, 0, GONE, GONE)
assertIconVisibility(spinner, 1, GONE, GONE)
assertIconVisibility(spinner, 2, GONE, GONE)
assertIconVisibility(spinner, 3, GONE, GONE)
}

@Test
fun videoStreams_Mixed() {
val adapter = StreamItemAdapter(
context,
getVideoStreams(true, true, true, true, true, false, true, true),
getAudioStreams(false, true, false, false, false, true, true, true)
)

spinner.adapter = adapter
assertIconVisibility(spinner, 0, VISIBLE, VISIBLE)
assertIconVisibility(spinner, 1, GONE, INVISIBLE)
assertIconVisibility(spinner, 2, VISIBLE, VISIBLE)
assertIconVisibility(spinner, 3, VISIBLE, VISIBLE)
assertIconVisibility(spinner, 4, VISIBLE, VISIBLE)
assertIconVisibility(spinner, 5, GONE, INVISIBLE)
assertIconVisibility(spinner, 6, GONE, INVISIBLE)
assertIconVisibility(spinner, 7, GONE, INVISIBLE)
}

@Test
fun subtitleStreams_noIcon() {
val adapter = StreamItemAdapter<SubtitlesStream, Stream>(
context,
StreamItemAdapter.StreamSizeWrapper(
(0 until 5).map {
SubtitlesStream(MediaFormat.SRT, "pt-BR", "https://example.com", false)
},
context
),
null
)
spinner.adapter = adapter
for (i in 0 until spinner.count) {
assertIconVisibility(spinner, i, GONE, GONE)
}
}

@Test
fun audioStreams_noIcon() {
val adapter = StreamItemAdapter<AudioStream, Stream>(
context,
StreamItemAdapter.StreamSizeWrapper(
(0 until 5).map { AudioStream("https://example.com/$it", MediaFormat.OPUS, 192) },
context
),
null
)
spinner.adapter = adapter
for (i in 0 until spinner.count) {
assertIconVisibility(spinner, i, GONE, GONE)
}
}

private fun getVideoStreams(vararg videoOnly: Boolean) =
StreamItemAdapter.StreamSizeWrapper(
videoOnly.map {
VideoStream("https://example.com", MediaFormat.MPEG_4, "720p", it)
},
context
)

private fun getAudioStreams(vararg shouldBeValid: Boolean) =
getSecondaryStreamsFromList(
shouldBeValid.map {
if (it) AudioStream("https://example.com", MediaFormat.OPUS, 192)
else null
}
)

private fun assertIconVisibility(
spinner: Spinner,
position: Int,
normalVisibility: Int,
dropDownVisibility: Int
) {
spinner.setSelection(position)
spinner.adapter.getView(position, null, spinner).run {
assertThat(
"normal visibility (pos=[$position])",
findViewById<View>(R.id.wo_sound_icon).visibility, `is`(normalVisibility)
)
}
spinner.adapter.getDropDownView(position, null, spinner).run {
assertThat(
"drop down visibility (pos=[$position])",
findViewById<View>(R.id.wo_sound_icon).visibility, `is`(dropDownVisibility)
)
}
}

private fun <T : Stream> getSecondaryStreamsFromList(streams: List<T?>) =
SparseArray<SecondaryStreamHelper<T>?>(streams.size).apply {
streams.forEachIndexed { index, stream ->
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
SecondaryStreamHelper(
StreamItemAdapter.StreamSizeWrapper(streams, context),
it
)
}
put(index, secondaryStreamHelper)
}
}
}
31 changes: 24 additions & 7 deletions app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,15 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA

private final StreamSizeWrapper<T> streamsWrapper;
private final SparseArray<SecondaryStreamHelper<U>> secondaryStreams;
private final boolean hasVideoOnlyWithNoSecondaryStream;

public StreamItemAdapter(final Context context, final StreamSizeWrapper<T> streamsWrapper,
final SparseArray<SecondaryStreamHelper<U>> secondaryStreams) {
this.context = context;
this.streamsWrapper = streamsWrapper;
this.secondaryStreams = secondaryStreams;
}

public StreamItemAdapter(final Context context, final StreamSizeWrapper<T> streamsWrapper,
final boolean showIconNoAudio) {
this(context, streamsWrapper, showIconNoAudio ? new SparseArray<>() : null);
this.hasVideoOnlyWithNoSecondaryStream = checkHasVideoOnlyWithNoSecondaryStream();
}

public StreamItemAdapter(final Context context, final StreamSizeWrapper<T> streamsWrapper) {
Expand Down Expand Up @@ -114,10 +112,11 @@ private View getCustomView(final int position, final View view, final ViewGroup
final VideoStream videoStream = ((VideoStream) stream);
qualityString = videoStream.getResolution();

if (secondaryStreams != null) {
if (hasVideoOnlyWithNoSecondaryStream) {
if (videoStream.isVideoOnly()) {
woSoundIconVisibility = secondaryStreams.get(position) == null ? View.VISIBLE
: View.INVISIBLE;
woSoundIconVisibility = hasSecondaryStream(position)
? (isDropdownItem ? View.INVISIBLE : View.GONE)
: View.VISIBLE;
} else if (isDropdownItem) {
woSoundIconVisibility = View.INVISIBLE;
}
Expand Down Expand Up @@ -171,6 +170,24 @@ private View getCustomView(final int position, final View view, final ViewGroup
return convertView;
}

private boolean hasSecondaryStream(final int position) {
return secondaryStreams != null && secondaryStreams.get(position) != null;
}

private boolean checkHasVideoOnlyWithNoSecondaryStream() {
for (int i = 0; i < streamsWrapper.getStreamsList().size(); i++) {
final T stream = streamsWrapper.getStreamsList().get(i);
if (stream instanceof VideoStream) {
final boolean videoOnly = ((VideoStream) stream).isVideoOnly();
if (videoOnly && !hasSecondaryStream(i)) {
return true;
}
}
}

return false;
}

/**
* A wrapper class that includes a way of storing the stream sizes.
*
Expand Down

0 comments on commit 363c37a

Please sign in to comment.