Unify fast-scroller

Completely unify the fast scroll indicators and the fast scroll thumb into a single view.
This commit is contained in:
OxygenCobalt 2021-03-23 14:34:23 -06:00
parent cc3d4fb9c6
commit 0a18108419
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
14 changed files with 112 additions and 205 deletions

View file

@ -68,9 +68,10 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:1.3.1" implementation "androidx.fragment:fragment-ktx:1.3.1"
// UI // UI
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.4" implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.dynamicanimation:dynamicanimation:1.0.0" implementation "androidx.dynamicanimation:dynamicanimation:1.0.0"
// Lifecycle // Lifecycle
def lifecycle_version = "2.3.0" def lifecycle_version = "2.3.0"
implementation "androidx.lifecycle:lifecycle-common:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common:$lifecycle_version"

View file

@ -78,6 +78,7 @@ class MusicStore private constructor() {
* Find a song from this instance in a safe manner. * Find a song from this instance in a safe manner.
* Using a normal search of the songs list runs the risk of getting the *wrong* song with * Using a normal search of the songs list runs the risk of getting the *wrong* song with
* the same name, so the album name is also used to fix the above problem. * the same name, so the album name is also used to fix the above problem.
* FIXME: Artist names are more unique than album names, use those
* @param name The name of the song * @param name The name of the song
* @param albumName The name of the song's album. * @param albumName The name of the song's album.
* @return The song requested, null if there isnt one. * @return The song requested, null if there isnt one.

View file

@ -1,78 +0,0 @@
package org.oxycblt.auxio.songs
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.util.AttributeSet
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.core.widget.TextViewCompat
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ViewScrollThumbBinding
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.inflater
/**
* The companion thumb for [FastScrollView]. This does not need any setup, instead pass it as an
* argument to [FastScrollView.setup].
* This code is fundamentally an adaptation of Reddit's IndicatorFastScroll, albeit specialized
* towards Auxio. The original library is here: https://github.com/reddit/IndicatorFastScroll/
* @author OxygenCobalt
*/
class FastScrollThumb @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val thumbAnim: SpringAnimation
private val binding = ViewScrollThumbBinding.inflate(context.inflater, this, true)
init {
val accent = Accent.get().getStateList(context)
binding.thumbLayout.apply {
backgroundTintList = accent
// Workaround for API 21 tint bug
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
(background as GradientDrawable).apply {
mutate()
color = accent
}
}
}
binding.thumbText.apply {
isVisible = true
TextViewCompat.setTextAppearance(this, R.style.TextAppearance_ThumbIndicator)
}
thumbAnim = SpringAnimation(binding.thumbLayout, DynamicAnimation.TRANSLATION_Y).apply {
spring = SpringForce().also {
it.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
}
}
visibility = View.INVISIBLE
isActivated = false
post {
visibility = View.VISIBLE
}
}
/**
* Make the thumb jump to a new position and update its text to the given [indicator].
* This is not meant for use outside of the main [FastScrollView] code. Do not use it.
*/
fun jumpTo(indicator: FastScrollView.Indicator, centerY: Int) {
binding.thumbText.text = indicator.char.toString()
thumbAnim.animateToFinalPosition(
centerY.toFloat() - (binding.thumbLayout.measuredHeight / 2)
)
}
}

View file

@ -4,22 +4,26 @@ import android.content.Context
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
import android.view.Gravity
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.MotionEvent import android.view.MotionEvent
import android.widget.LinearLayout import androidx.constraintlayout.widget.ConstraintLayout
import android.widget.TextView import androidx.core.view.isVisible
import androidx.appcompat.widget.AppCompatTextView import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.core.widget.TextViewCompat import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ViewFastScrollBinding
import org.oxycblt.auxio.logD import org.oxycblt.auxio.logD
import org.oxycblt.auxio.ui.Accent import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.canScroll
import org.oxycblt.auxio.ui.inflater
import org.oxycblt.auxio.ui.resolveAttr import org.oxycblt.auxio.ui.resolveAttr
import org.oxycblt.auxio.ui.toColor import org.oxycblt.auxio.ui.toColor
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt
/** /**
* A view that allows for quick scrolling through a [RecyclerView] with many items. Unlike other * A view that allows for quick scrolling through a [RecyclerView] with many items. Unlike other
@ -32,22 +36,23 @@ class FastScrollView @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = -1 defStyleAttr: Int = -1
) : LinearLayout(context, attrs, defStyleAttr) { ) : ConstraintLayout(context, attrs, defStyleAttr) {
// --- BASIC SETUP --- // --- UI ---
private val binding = ViewFastScrollBinding.inflate(context.inflater, this, true)
private val thumbAnim: SpringAnimation
// --- RECYCLER ---
private var mRecycler: RecyclerView? = null private var mRecycler: RecyclerView? = null
private var mThumb: FastScrollThumb? = null
private var mGetItem: ((Int) -> Char)? = null private var mGetItem: ((Int) -> Char)? = null
// --- INDICATORS --- // --- INDICATORS ---
/** Representation of a single Indicator character in the view */ private data class Indicator(val char: Char, val pos: Int)
data class Indicator(val char: Char, val pos: Int)
private var indicators = listOf<Indicator>() private var indicators = listOf<Indicator>()
private val indicatorText: TextView
private val activeColor = Accent.get().color.toColor(context) private val activeColor = Accent.get().color.toColor(context)
private val inactiveColor = android.R.attr.textColorSecondary.resolveAttr(context) private val inactiveColor = android.R.attr.textColorSecondary.resolveAttr(context)
@ -59,34 +64,21 @@ class FastScrollView @JvmOverloads constructor(
init { init {
isFocusableInTouchMode = true isFocusableInTouchMode = true
isClickable = true isClickable = true
gravity = Gravity.CENTER
val textPadding = TypedValue.applyDimension( thumbAnim = SpringAnimation(binding.scrollThumb, DynamicAnimation.TRANSLATION_Y).apply {
TypedValue.COMPLEX_UNIT_DIP, 4F, resources.displayMetrics spring = SpringForce().also {
) it.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
}
// Making this entire view a TextView will cause distortions due to the touch calculations
// using a height that is not wrapped to the text.
indicatorText = AppCompatTextView(context).apply {
gravity = Gravity.CENTER
includeFontPadding = false
TextViewCompat.setTextAppearance(this, R.style.TextAppearance_FastScroll)
setLineSpacing(textPadding, lineSpacingMultiplier)
setTextColor(inactiveColor)
} }
addView(indicatorText)
} }
/** /**
* Set up this view with a [RecyclerView] and a corresponding [FastScrollThumb]. * Set up this view with a [RecyclerView] and a corresponding [FastScrollThumb].
*/ */
fun setup(recycler: RecyclerView, thumb: FastScrollThumb, getItem: (Int) -> Char) { fun setup(recycler: RecyclerView, getItem: (Int) -> Char) {
check(mRecycler == null) { "Only set up this view once." } check(mRecycler == null) { "Only set up this view once." }
mRecycler = recycler mRecycler = recycler
mThumb = thumb
mGetItem = getItem mGetItem = getItem
postIndicatorUpdate() postIndicatorUpdate()
@ -105,6 +97,9 @@ class FastScrollView @JvmOverloads constructor(
updateIndicators() updateIndicators()
} }
// Hide this view if there is nothing to scroll
isVisible = recycler.canScroll()
hasPostedItemUpdate = false hasPostedItemUpdate = false
} }
} }
@ -140,9 +135,9 @@ class FastScrollView @JvmOverloads constructor(
} }
} }
indicatorText.apply { // Then set it as the unified TextView text, for efficiency purposes.
tag = indicators binding.scrollIndicatorText.text = indicators.joinToString("\n") { indicator ->
text = indicators.joinToString("\n") { it.char.toString() } indicator.char.toString()
} }
} }
@ -152,37 +147,38 @@ class FastScrollView @JvmOverloads constructor(
override fun onTouchEvent(event: MotionEvent): Boolean { override fun onTouchEvent(event: MotionEvent): Boolean {
performClick() performClick()
val success = handleTouch(event.action, event.y.toInt()) val success = handleTouch(event.action, event.y.roundToInt())
// Depending on the results, update the visibility of the thumb and the pressed state of // Depending on the results, update the visibility of the thumb and the pressed state of
// this view. // this view.
isPressed = success isPressed = success
mThumb?.isActivated = success binding.scrollThumb.isActivated = success
return success return success
} }
@Suppress("UNCHECKED_CAST")
private fun handleTouch(action: Int, touchY: Int): Boolean { private fun handleTouch(action: Int, touchY: Int): Boolean {
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
indicatorText.setTextColor(inactiveColor) binding.scrollIndicatorText.setTextColor(inactiveColor)
lastPos = -1 lastPos = -1
return false return false
} }
if (touchY in (indicatorText.top until indicatorText.bottom)) { // Try to figure out which indicator the pointer has landed on
// Try to roughly caculate which indicator the user is currently touching [Since the if (touchY in (binding.scrollIndicatorText.top until binding.scrollIndicatorText.bottom)) {
val textHeight = indicatorText.height / indicators.size // Get the touch position in regards to the TextView and the rough text height
val indicatorIndex = min( val indicatorTouchY = touchY - binding.scrollIndicatorText.top
(touchY - indicatorText.top) / textHeight, indicators.lastIndex val textHeight = binding.scrollIndicatorText.height / indicators.size
)
val centerY = y.toInt() + (textHeight / 2) + (indicatorIndex * textHeight) // Use that to calculate the indicator index, if the calculation is
// invalid just ignore it.
val index = min(indicatorTouchY / textHeight, indicators.lastIndex)
val touchedIndicator = indicators[indicatorIndex] // Also calculate the rough center position of the indicator for the scroll thumb
val centerY = binding.scrollIndicatorText.y + (textHeight / 2) + (index * textHeight)
selectIndicator(touchedIndicator, centerY) selectIndicator(indicators[index], centerY)
return true return true
} }
@ -190,20 +186,20 @@ class FastScrollView @JvmOverloads constructor(
return false return false
} }
private fun selectIndicator(indicator: Indicator, indicatorCenterY: Int) { private fun selectIndicator(indicator: Indicator, centerY: Float) {
if (indicator.pos != lastPos) { if (indicator.pos != lastPos) {
lastPos = indicator.pos lastPos = indicator.pos
indicatorText.setTextColor(activeColor) binding.scrollIndicatorText.setTextColor(activeColor)
// Stop any scroll momentum and snap-scroll to the position // Stop any scroll momentum and snap-scroll to the position
mRecycler?.apply { mRecycler?.apply {
stopScroll() stopScroll()
(layoutManager as LinearLayoutManager).scrollToPositionWithOffset( (layoutManager as LinearLayoutManager).scrollToPositionWithOffset(indicator.pos, 0)
indicator.pos, 0
)
} }
mThumb?.jumpTo(indicator, indicatorCenterY) // Update the thumb position/text
binding.scrollThumbText.text = indicator.char.toString()
thumbAnim.animateToFinalPosition(centerY - (binding.scrollThumb.measuredHeight / 2))
performHapticFeedback( performHapticFeedback(
if (Build.VERSION.SDK_INT >= 27) { if (Build.VERSION.SDK_INT >= 27) {

View file

@ -12,7 +12,6 @@ import org.oxycblt.auxio.databinding.FragmentSongsBinding
import org.oxycblt.auxio.logD import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.canScroll
import org.oxycblt.auxio.ui.fixAnimInfoLeak import org.oxycblt.auxio.ui.fixAnimInfoLeak
import org.oxycblt.auxio.ui.getSpans import org.oxycblt.auxio.ui.getSpans
import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.ui.newMenu
@ -55,17 +54,9 @@ class SongsFragment : Fragment() {
if (spans != 1) { if (spans != 1) {
layoutManager = GridLayoutManager(requireContext(), spans) layoutManager = GridLayoutManager(requireContext(), spans)
} }
post {
if (!canScroll()) {
// Disable fast scrolling if there is nothing to scroll
binding.songFastScroll.visibility = View.GONE
binding.songFastScrollThumb.visibility = View.GONE
}
}
} }
binding.songFastScroll.setup(binding.songRecycler, binding.songFastScrollThumb) { pos -> binding.songFastScroll.setup(binding.songRecycler) { pos ->
val char = musicStore.songs[pos].name.first val char = musicStore.songs[pos].name.first
if (char.isDigit()) '#' else char if (char.isDigit()) '#' else char

View file

@ -84,7 +84,7 @@ fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
* @param T The system service in question. * @param T The system service in question.
* @param serviceClass The service's kotlin class [Java class will be used in function call] * @param serviceClass The service's kotlin class [Java class will be used in function call]
* @return The system service * @return The system service
* @throws IllegalStateException If the system service cannot be retrieved. * @throws IllegalArgumentException If the system service cannot be retrieved.
*/ */
fun <T : Any> Context.getSystemServiceSafe(serviceClass: KClass<T>): T { fun <T : Any> Context.getSystemServiceSafe(serviceClass: KClass<T>): T {
return requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) { return requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) {
@ -218,7 +218,6 @@ fun Activity.isIrregularLandscape(): Boolean {
* Check if the system bars are on the bottom. * Check if the system bars are on the bottom.
* @return If the system bars are on the bottom, false if no. * @return If the system bars are on the bottom, false if no.
*/ */
@Suppress("DEPRECATION")
private fun isSystemBarOnBottom(activity: Activity): Boolean { private fun isSystemBarOnBottom(activity: Activity): Boolean {
val realPoint = Point() val realPoint = Point()
val metrics = DisplayMetrics() val metrics = DisplayMetrics()
@ -236,6 +235,7 @@ private fun isSystemBarOnBottom(activity: Activity): Boolean {
} }
} }
} else { } else {
@Suppress("DEPRECATION")
(activity.getSystemServiceSafe(WindowManager::class)).apply { (activity.getSystemServiceSafe(WindowManager::class)).apply {
defaultDisplay.getRealSize(realPoint) defaultDisplay.getRealSize(realPoint)
defaultDisplay.getMetrics(metrics) defaultDisplay.getMetrics(metrics)

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/white" />
</shape>

View file

@ -39,13 +39,5 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/song_toolbar" /> app:layout_constraintTop_toBottomOf="@+id/song_toolbar" />
<org.oxycblt.auxio.songs.FastScrollThumb
android:id="@+id/song_fast_scroll_thumb"
android:layout_width="@dimen/width_thumb_view"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_fast_scroll"
app:layout_constraintTop_toBottomOf="@+id/song_toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<merge
tools:layout_height="match_parent"
tools:layout_width="match_parent"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<FrameLayout
android:id="@+id/scroll_thumb"
android:layout_width="@dimen/size_scroll_thumb"
android:layout_height="@dimen/size_scroll_thumb"
android:background="@drawable/ui_circle"
android:elevation="@dimen/elevation_small"
android:backgroundTint="?attr/colorPrimary"
android:stateListAnimator="@animator/animator_thumb"
app:layout_constraintEnd_toStartOf="@+id/scroll_indicator_text">
<TextView
android:id="@+id/scroll_thumb_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="@dimen/text_size_thumb"
android:textColor="?android:attr/windowBackground"
android:fontFamily="@font/inter_semibold"
tools:text="A" />
</FrameLayout>
<TextView
android:id="@+id/scroll_indicator_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter_semibold"
android:gravity="center"
android:includeFontPadding="false"
android:lineSpacingExtra="@dimen/padding_tiny"
android:paddingTop="@dimen/padding_tiny"
android:paddingBottom="@dimen/padding_tiny"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="A\nB\n\C\nD\nE" />
</merge>
</layout>

View file

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Animator from IndicatorFastScroll (https://github.com/reddit/IndicatorFastScroll) -->
<layout>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:layout_height="match_parent"
tools:layout_width="@dimen/size_scroll_thumb"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<FrameLayout
android:id="@+id/thumb_layout"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/ui_circular_button"
android:elevation="@dimen/elevation_small"
android:stateListAnimator="@animator/animator_thumb"
app:layout_constraintDimensionRatio="W,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/thumb_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:text="A" />
</FrameLayout>
</merge>
</layout>

View file

@ -24,11 +24,6 @@
android:name="org.oxycblt.auxio.MainFragment" android:name="org.oxycblt.auxio.MainFragment"
android:label="MainFragment" android:label="MainFragment"
tools:layout="@layout/fragment_main"> tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_return_to_loading"
app:destination="@id/loading_fragment"
app:popUpTo="@id/main_fragment"
app:popUpToInclusive="true" />
<action <action
android:id="@+id/action_go_to_playback" android:id="@+id/action_go_to_playback"
app:destination="@id/playback_fragment" app:destination="@id/playback_fragment"

View file

@ -40,7 +40,7 @@
<dimen name="size_play_pause">70dp</dimen> <dimen name="size_play_pause">70dp</dimen>
<dimen name="size_play_pause_compact">36dp</dimen> <dimen name="size_play_pause_compact">36dp</dimen>
<dimen name="size_scroll_thumb">32dp</dimen> <dimen name="size_scroll_thumb">48dp</dimen>
<dimen name="size_clear">32dp</dimen> <dimen name="size_clear">32dp</dimen>
<dimen name="size_app_icon">60dp</dimen> <dimen name="size_app_icon">60dp</dimen>

View file

@ -98,18 +98,6 @@
<item name="android:fontFamily">@font/inter_exbold</item> <item name="android:fontFamily">@font/inter_exbold</item>
</style> </style>
<!-- Fast scroll text appearance -->
<style name="TextAppearance.FastScroll" parent="TextAppearance.AppCompat.Body2">
<item name="android:fontFamily">@font/inter_semibold</item>
<item name="android:lineSpacingExtra">@dimen/padding_tiny</item>
</style>
<!-- Fast scroll thumb appearance -->
<style name="TextAppearance.ThumbIndicator" parent="TextAppearance.FastScroll">
<item name="android:textSize">@dimen/text_size_thumb</item>
<item name="android:textColor">?android:attr/windowBackground</item>
</style>
<!-- Style for the general item background --> <!-- Style for the general item background -->
<style name="ItemSurroundings"> <style name="ItemSurroundings">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>

View file

@ -1,23 +1,21 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.4.31' ext.kotlin_version = "1.4.31"
ext.navigation_version = "2.3.4" ext.navigation_version = "2.3.4"
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
// TODO: Eliminate Exoplayer when it migrates to GMaven
jcenter { jcenter {
content { content {
includeGroup("org.jetbrains.trove4j") includeGroup("org.jetbrains.trove4j")
includeGroup("com.google.android.exoplayer")
} }
} }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.3' classpath "com.android.tools.build:gradle:4.1.3"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
@ -31,9 +29,9 @@ allprojects {
google() google()
mavenCentral() mavenCentral()
// TODO: Eliminate Exoplayer when it migrates to GMaven
jcenter { jcenter {
content { content {
includeGroup("org.jetbrains.trove4j")
includeGroup("com.google.android.exoplayer") includeGroup("com.google.android.exoplayer")
} }
} }