Improved slider

main
h4h13 2020-02-25 04:03:05 +05:30
parent 437f73b1dc
commit c42e9cb0df
68 changed files with 621 additions and 3401 deletions

61
.github/stale.yml vendored
View File

@ -1,61 +0,0 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- pinned
- security
- "[Status] Maybe Later"
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
# closeComment: >
# Your comment here.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
# daysUntilStale: 30
# markComment: >
# This pull request has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
# issues:
# exemptLabels:
# - confirmed

View File

@ -14,6 +14,8 @@ proguardDictionaries {
android { android {
compileSdkVersion 29 compileSdkVersion 29
buildToolsVersion = '29.0.2'
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
@ -81,7 +83,7 @@ android {
sourceCompatibility '1.8' sourceCompatibility '1.8'
targetCompatibility '1.8' targetCompatibility '1.8'
} }
buildToolsVersion = '29.0.2'
configurations.all { configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9' resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
@ -130,10 +132,10 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.fragment:fragment:1.2.1' implementation 'androidx.fragment:fragment:1.2.2'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha04' implementation 'com.google.android.material:material:1.2.0-alpha05'
def retrofit_version = "2.6.2" def retrofit_version = "2.6.2"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
@ -160,7 +162,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
def kotlin_coroutines_version = "1.3.0" def kotlin_coroutines_version = "1.3.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"

View File

@ -51,8 +51,8 @@ import code.name.monkey.retromusic.fragments.mainactivity.GenresFragment;
import code.name.monkey.retromusic.fragments.mainactivity.PlayingQueueFragment; import code.name.monkey.retromusic.fragments.mainactivity.PlayingQueueFragment;
import code.name.monkey.retromusic.fragments.mainactivity.PlaylistsFragment; import code.name.monkey.retromusic.fragments.mainactivity.PlaylistsFragment;
import code.name.monkey.retromusic.fragments.mainactivity.SongsFragment; import code.name.monkey.retromusic.fragments.mainactivity.SongsFragment;
import code.name.monkey.retromusic.fragments.mainactivity.folders.FoldersFragment; import code.name.monkey.retromusic.fragments.mainactivity.FoldersFragment;
import code.name.monkey.retromusic.fragments.mainactivity.home.BannerHomeFragment; import code.name.monkey.retromusic.fragments.mainactivity.BannerHomeFragment;
import code.name.monkey.retromusic.helper.MusicPlayerRemote; import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import code.name.monkey.retromusic.helper.SearchQueryHelper; import code.name.monkey.retromusic.helper.SearchQueryHelper;
import code.name.monkey.retromusic.helper.SortOrder.AlbumSortOrder; import code.name.monkey.retromusic.helper.SortOrder.AlbumSortOrder;

View File

@ -26,7 +26,7 @@ import code.name.monkey.retromusic.fragments.mainactivity.ArtistsFragment
import code.name.monkey.retromusic.fragments.mainactivity.GenresFragment import code.name.monkey.retromusic.fragments.mainactivity.GenresFragment
import code.name.monkey.retromusic.fragments.mainactivity.PlaylistsFragment import code.name.monkey.retromusic.fragments.mainactivity.PlaylistsFragment
import code.name.monkey.retromusic.fragments.mainactivity.SongsFragment import code.name.monkey.retromusic.fragments.mainactivity.SongsFragment
import code.name.monkey.retromusic.fragments.mainactivity.home.BannerHomeFragment import code.name.monkey.retromusic.fragments.mainactivity.BannerHomeFragment
import dagger.Component import dagger.Component
import javax.inject.Singleton import javax.inject.Singleton

View File

@ -21,7 +21,7 @@ import android.widget.EditText
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import com.google.android.material.slider.Slider
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : View> ViewGroup.inflate(@LayoutRes layout: Int): T { fun <T : View> ViewGroup.inflate(@LayoutRes layout: Int): T {
@ -45,4 +45,10 @@ fun View.showOrHide(show: Boolean) = if (show) show() else hide()
fun EditText.appHandleColor(): EditText { fun EditText.appHandleColor(): EditText {
TintHelper.colorHandles(this, ThemeStore.accentColor(context)) TintHelper.colorHandles(this, ThemeStore.accentColor(context))
return this return this
}
fun Slider.setRange(progress: Float, to: Float) {
valueFrom = 0F
valueTo = to
value = progress
} }

View File

@ -11,20 +11,19 @@ enum class NowPlayingScreen constructor(
val id: Int val id: Int
) { ) {
NORMAL(R.string.normal, R.drawable.np_normal, 0),
FLAT(R.string.flat, R.drawable.np_flat, 1),
FIT(R.string.fit, R.drawable.np_fit, 12),
TINY(R.string.tiny, R.drawable.np_tiny, 7),
PEAK(R.string.peak, R.drawable.np_peak, 14),
ADAPTIVE(R.string.adaptive, R.drawable.np_adaptive, 10), ADAPTIVE(R.string.adaptive, R.drawable.np_adaptive, 10),
BLUR(R.string.blur, R.drawable.np_blur, 4), BLUR(R.string.blur, R.drawable.np_blur, 4),
BLUR_CARD(R.string.blur_card, R.drawable.np_blur_card, 9), BLUR_CARD(R.string.blur_card, R.drawable.np_blur_card, 9),
CARD(R.string.card, R.drawable.np_card, 6), CARD(R.string.card, R.drawable.np_card, 6),
COLOR(R.string.color, R.drawable.np_color, 5),
CIRCLE(R.string.circle, R.drawable.np_minimalistic_circle, 15), CIRCLE(R.string.circle, R.drawable.np_minimalistic_circle, 15),
COLOR(R.string.color, R.drawable.np_color, 5),
FIT(R.string.fit, R.drawable.np_fit, 12),
FLAT(R.string.flat, R.drawable.np_flat, 1),
FULL(R.string.full, R.drawable.np_full, 2), FULL(R.string.full, R.drawable.np_full, 2),
MATERIAL(R.string.material, R.drawable.np_material, 11), MATERIAL(R.string.material, R.drawable.np_material, 11),
NORMAL(R.string.normal, R.drawable.np_normal, 0),
PEAK(R.string.peak, R.drawable.np_peak, 14),
PLAIN(R.string.plain, R.drawable.np_plain, 3), PLAIN(R.string.plain, R.drawable.np_plain, 3),
SIMPLE(R.string.simple, R.drawable.np_simple, 8), SIMPLE(R.string.simple, R.drawable.np_simple, 8),
TINY(R.string.tiny, R.drawable.np_tiny, 7),
} }

View File

@ -8,7 +8,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.SeekBar
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
@ -17,12 +16,14 @@ import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import code.name.monkey.retromusic.volume.AudioVolumeObserver import code.name.monkey.retromusic.volume.AudioVolumeObserver
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener
import com.google.android.material.slider.Slider
import com.google.android.material.slider.Slider.OnChangeListener
import kotlinx.android.synthetic.main.fragment_volume.volumeDown import kotlinx.android.synthetic.main.fragment_volume.volumeDown
import kotlinx.android.synthetic.main.fragment_volume.volumeSeekBar import kotlinx.android.synthetic.main.fragment_volume.volumeSeekBar
import kotlinx.android.synthetic.main.fragment_volume.volumeUp import kotlinx.android.synthetic.main.fragment_volume.volumeUp
class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolumeChangedListener, class VolumeFragment : Fragment(), OnAudioVolumeChangedListener,
View.OnClickListener { View.OnClickListener, OnChangeListener {
private var audioVolumeObserver: AudioVolumeObserver? = null private var audioVolumeObserver: AudioVolumeObserver? = null
@ -51,20 +52,22 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
val audioManager = audioManager val audioManager = audioManager
if (audioManager != null) { if (audioManager != null) {
volumeSeekBar.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) volumeSeekBar.valueTo = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat()
volumeSeekBar.progress = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) volumeSeekBar.value = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat()
} }
volumeSeekBar.setOnSeekBarChangeListener(this) volumeSeekBar.addOnChangeListener(this)
} }
override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) { override fun onAudioVolumeChanged(currentVolume: Float, maxVolume: Float) {
if (volumeSeekBar == null) { if (volumeSeekBar == null) {
return return
} }
if (maxVolume <= 0) {
volumeSeekBar.max = maxVolume return
volumeSeekBar.progress = currentVolume }
volumeDown.setImageResource(if (currentVolume == 0) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp) volumeSeekBar.valueTo = maxVolume
volumeSeekBar.valueFrom = currentVolume
volumeDown.setImageResource(if (currentVolume == 0.0f) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp)
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -74,19 +77,6 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
} }
} }
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
val audioManager = audioManager
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC, i, 0)
setPauseWhenZeroVolume(i < 1)
volumeDown?.setImageResource(if (i == 0) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
}
override fun onClick(view: View) { override fun onClick(view: View) {
val audioManager = audioManager val audioManager = audioManager
when (view.id) { when (view.id) {
@ -111,10 +101,6 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
ViewUtil.setProgressDrawable(volumeSeekBar, color, true) ViewUtil.setProgressDrawable(volumeSeekBar, color, true)
} }
fun removeThumb() {
volumeSeekBar.thumb = null
}
private fun setPauseWhenZeroVolume(pauseWhenZeroVolume: Boolean) { private fun setPauseWhenZeroVolume(pauseWhenZeroVolume: Boolean) {
if (PreferenceUtil.getInstance(requireContext()).pauseOnZeroVolume()) if (MusicPlayerRemote.isPlaying && pauseWhenZeroVolume) { if (PreferenceUtil.getInstance(requireContext()).pauseOnZeroVolume()) if (MusicPlayerRemote.isPlaying && pauseWhenZeroVolume) {
MusicPlayerRemote.pauseSong() MusicPlayerRemote.pauseSong()
@ -134,4 +120,14 @@ class VolumeFragment : Fragment(), SeekBar.OnSeekBarChangeListener, OnAudioVolum
return VolumeFragment() return VolumeFragment()
} }
} }
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
if (value <= 0) {
return
}
val audioManager = audioManager
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC, value.toInt(), 0)
setPauseWhenZeroVolume(value < 1.0f)
volumeDown.setImageResource(if (value == 0.0f) R.drawable.ic_volume_off_white_24dp else R.drawable.ic_volume_down_white_24dp)
}
} }

View File

@ -1,4 +1,18 @@
package code.name.monkey.retromusic.fragments.mainactivity.home /*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.fragments.mainactivity
import android.app.ActivityOptions import android.app.ActivityOptions
import android.os.Bundle import android.os.Bundle

View File

@ -1,4 +1,18 @@
package code.name.monkey.retromusic.fragments.mainactivity.folders; /*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.fragments.mainactivity;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;

View File

@ -1,13 +1,10 @@
package code.name.monkey.retromusic.fragments.player.adaptive package code.name.monkey.retromusic.fragments.player.adaptive
import android.animation.ObjectAnimator
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
@ -16,12 +13,12 @@ import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
@ -212,13 +209,10 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() {
} }
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total if (total <= 0) {
return
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) }
animator.duration = SLIDER_ANIMATION_TIME progressSlider.setRange(progress.toFloat(), total.toFloat())
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
@ -232,16 +226,14 @@ class AdaptivePlaybackControlsFragment : AbsPlayerControlsFragment() {
} }
override fun setUpProgressSlider() { override fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { progressSlider.addOnChangeListener { _, value, fromUser ->
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) {
if (fromUser) { MusicPlayerRemote.seekTo(value.toInt())
MusicPlayerRemote.seekTo(progress) onUpdateProgressViews(
onUpdateProgressViews( MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis
MusicPlayerRemote.songDurationMillis )
)
}
} }
}) }
} }
} }

View File

@ -1,6 +1,5 @@
package code.name.monkey.retromusic.fragments.player.blur package code.name.monkey.retromusic.fragments.player.blur
import android.animation.ObjectAnimator
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
@ -9,20 +8,18 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
@ -235,20 +232,6 @@ class BlurPlaybackControlsFragment : AbsPlayerControlsFragment() {
} }
} }
override fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
})
}
private fun showBonceAnimation() { private fun showBonceAnimation() {
playPauseButton.apply { playPauseButton.apply {
clearAnimation() clearAnimation()
@ -273,14 +256,23 @@ class BlurPlaybackControlsFragment : AbsPlayerControlsFragment() {
} }
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total if (total <= 0) {
return
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) }
animator.duration = SLIDER_ANIMATION_TIME progressSlider.setRange(progress.toFloat(), total.toFloat())
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
override fun setUpProgressSlider() {
progressSlider.addOnChangeListener { _, value, fromUser ->
if (fromUser) {
MusicPlayerRemote.seekTo(value.toInt())
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
}
} }

View File

@ -1,6 +1,5 @@
package code.name.monkey.retromusic.fragments.player.cardblur package code.name.monkey.retromusic.fragments.player.cardblur
import android.animation.ObjectAnimator
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
@ -8,19 +7,17 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
@ -210,29 +207,24 @@ class CardBlurPlaybackControlsFragment : AbsPlayerControlsFragment() {
} }
} }
override fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
})
}
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total if (total <= 0) {
return
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) }
animator.duration = SLIDER_ANIMATION_TIME progressSlider.setRange(progress.toFloat(), total.toFloat())
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
override fun setUpProgressSlider() {
progressSlider.addOnChangeListener { _, value, fromUser ->
if (fromUser) {
MusicPlayerRemote.seekTo(value.toInt())
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
}
} }

View File

@ -14,7 +14,6 @@
package code.name.monkey.retromusic.fragments.player.circle package code.name.monkey.retromusic.fragments.player.circle
import android.animation.ObjectAnimator
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
@ -23,8 +22,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
@ -32,14 +29,13 @@ import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
@ -100,14 +96,7 @@ class CirclePlayerFragment : AbsPlayerFragment(), Callback, OnAudioVolumeChanged
} }
private fun setupViews() { private fun setupViews() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { setUpProgressSlider()
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis)
}
}
})
ViewUtil.setProgressDrawable(progressSlider, ThemeStore.accentColor(requireContext())) ViewUtil.setProgressDrawable(progressSlider, ThemeStore.accentColor(requireContext()))
volumeSeekBar.progressColor = ThemeStore.accentColor(requireContext()) volumeSeekBar.progressColor = ThemeStore.accentColor(requireContext())
setUpPlayPauseFab() setUpPlayPauseFab()
@ -205,18 +194,6 @@ class CirclePlayerFragment : AbsPlayerFragment(), Callback, OnAudioVolumeChanged
} }
} }
override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
private fun updatePlayPauseDrawableState() { private fun updatePlayPauseDrawableState() {
when { when {
MusicPlayerRemote.isPlaying -> playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp) MusicPlayerRemote.isPlaying -> playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp)
@ -224,12 +201,13 @@ class CirclePlayerFragment : AbsPlayerFragment(), Callback, OnAudioVolumeChanged
} }
} }
override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) { override fun onAudioVolumeChanged(currentVolume: Float, maxVolume: Float) {
if (volumeSeekBar == null) { if (volumeSeekBar == null) {
return return
} }
volumeSeekBar.max = maxVolume
volumeSeekBar.progress = currentVolume volumeSeekBar.max = maxVolume.toInt()
volumeSeekBar.progress = currentVolume.toInt()
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -249,4 +227,25 @@ class CirclePlayerFragment : AbsPlayerFragment(), Callback, OnAudioVolumeChanged
override fun onStopTrackingTouch(seekArc: SeekArc?) { override fun onStopTrackingTouch(seekArc: SeekArc?) {
} }
override fun onUpdateProgressViews(progress: Int, total: Int) {
if (total <= 0) {
return
}
progressSlider.setRange(progress.toFloat(), total.toFloat())
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
fun setUpProgressSlider() {
progressSlider.addOnChangeListener { _, value, fromUser ->
if (fromUser) {
MusicPlayerRemote.seekTo(value.toInt())
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
}
} }

View File

@ -1,6 +1,5 @@
package code.name.monkey.retromusic.fragments.player.color package code.name.monkey.retromusic.fragments.player.color
import android.animation.ObjectAnimator
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
@ -8,33 +7,21 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.nextButton import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.*
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.playPauseButton
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.previousButton
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.progressSlider
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.repeatButton
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.shuffleButton
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.songCurrentProgress
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.songInfo
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.songTotalTime
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.text
import kotlinx.android.synthetic.main.fragment_color_player_playback_controls.title
class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() { class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
@ -47,7 +34,11 @@ class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_color_player_playback_controls, container, false) return inflater.inflate(R.layout.fragment_color_player_playback_controls, container, false)
} }
@ -173,7 +164,10 @@ class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
@ -185,7 +179,10 @@ class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
@ -215,26 +212,24 @@ class ColorPlaybackControlsFragment : AbsPlayerControlsFragment() {
} }
} }
override fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis)
}
}
})
}
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider!!.max = total if (total <= 0) {
return
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) }
animator.duration = SLIDER_ANIMATION_TIME progressSlider.setRange(progress.toFloat(), total.toFloat())
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
override fun setUpProgressSlider() {
progressSlider.addOnChangeListener { _, value, fromUser ->
if (fromUser) {
MusicPlayerRemote.seekTo(value.toInt())
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
}
} }

View File

@ -27,17 +27,7 @@ import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.nextButton import kotlinx.android.synthetic.main.fragment_fit_playback_controls.*
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.playPauseButton
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.previousButton
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.progressSlider
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.repeatButton
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.shuffleButton
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.songCurrentProgress
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.songInfo
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.songTotalTime
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.text
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.title
class FitPlaybackControlsFragment : AbsPlayerControlsFragment() { class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
@ -126,10 +116,12 @@ class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
val colorBg = ATHUtil.resolveColor(activity, android.R.attr.colorBackground) val colorBg = ATHUtil.resolveColor(activity, android.R.attr.colorBackground)
if (ColorUtil.isColorLight(colorBg)) { if (ColorUtil.isColorLight(colorBg)) {
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(activity, true) lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(activity, true)
lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(activity, true) lastDisabledPlaybackControlsColor =
MaterialValueHelper.getSecondaryDisabledTextColor(activity, true)
} else { } else {
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(activity, false) lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(activity, false)
lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(activity, false) lastDisabledPlaybackControlsColor =
MaterialValueHelper.getPrimaryDisabledTextColor(activity, false)
} }
val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) { val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) {
@ -196,7 +188,10 @@ class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
@ -208,7 +203,10 @@ class FitPlaybackControlsFragment : AbsPlayerControlsFragment() {
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)

View File

@ -1,14 +1,11 @@
package code.name.monkey.retromusic.fragments.player.flat package code.name.monkey.retromusic.fragments.player.flat
import android.animation.ObjectAnimator
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
@ -17,26 +14,18 @@ import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.playPauseButton import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.*
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.progressSlider
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.repeatButton
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.shuffleButton
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.songCurrentProgress
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.songInfo
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.songTotalTime
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.text
import kotlinx.android.synthetic.main.fragment_flat_player_playback_controls.title
class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback { class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
@ -73,18 +62,6 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
progressViewUpdateHelper.stop() progressViewUpdateHelper.stop()
} }
override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
animator.duration = SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
public override fun show() { public override fun show() {
playPauseButton!!.animate() playPauseButton!!.animate()
.scaleX(1f) .scaleX(1f)
@ -106,10 +83,12 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
val isDark = ColorUtil.isColorLight(colorBg) val isDark = ColorUtil.isColorLight(colorBg)
if (isDark) { if (isDark) {
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(activity, true) lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(activity, true)
lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(activity, true) lastDisabledPlaybackControlsColor =
MaterialValueHelper.getSecondaryDisabledTextColor(activity, true)
} else { } else {
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(activity, false) lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(activity, false)
lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(activity, false) lastDisabledPlaybackControlsColor =
MaterialValueHelper.getPrimaryDisabledTextColor(activity, false)
} }
val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) { val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) {
@ -131,7 +110,8 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
val isDark = ColorUtil.isColorLight(color) val isDark = ColorUtil.isColorLight(color)
val darkColor = ColorUtil.darkenColor(color) val darkColor = ColorUtil.darkenColor(color)
val colorPrimary = MaterialValueHelper.getPrimaryTextColor(context, isDark) val colorPrimary = MaterialValueHelper.getPrimaryTextColor(context, isDark)
val colorSecondary = MaterialValueHelper.getSecondaryTextColor(context, ColorUtil.isColorLight(darkColor)) val colorSecondary =
MaterialValueHelper.getSecondaryTextColor(context, ColorUtil.isColorLight(darkColor))
TintHelper.setTintAuto(playPauseButton!!, colorPrimary, false) TintHelper.setTintAuto(playPauseButton!!, colorPrimary, false)
TintHelper.setTintAuto(playPauseButton!!, color, true) TintHelper.setTintAuto(playPauseButton!!, color, true)
@ -191,18 +171,25 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
} }
} }
override fun onUpdateProgressViews(progress: Int, total: Int) {
if (total <= 0) {
return
}
progressSlider.setRange(progress.toFloat(), total.toFloat())
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
override fun setUpProgressSlider() { override fun setUpProgressSlider() {
progressSlider!!.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { progressSlider.addOnChangeListener { _, value, fromUser ->
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) {
if (fromUser) { MusicPlayerRemote.seekTo(value.toInt())
MusicPlayerRemote.seekTo(progress) onUpdateProgressViews(
onUpdateProgressViews( MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis
MusicPlayerRemote.songDurationMillis )
)
}
} }
}) }
} }
override fun onRepeatModeChanged() { override fun onRepeatModeChanged() {
@ -221,7 +208,10 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
@ -244,7 +234,10 @@ class FlatPlaybackControlsFragment : AbsPlayerControlsFragment(), Callback {
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
} }

View File

@ -1,6 +1,5 @@
package code.name.monkey.retromusic.fragments.player.full package code.name.monkey.retromusic.fragments.player.full
import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
@ -12,9 +11,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.SeekBar
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
@ -23,36 +20,25 @@ import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import kotlinx.android.synthetic.main.fragment_full_player_controls.nextButton import kotlinx.android.synthetic.main.fragment_full_player_controls.*
import kotlinx.android.synthetic.main.fragment_full_player_controls.playPauseButton
import kotlinx.android.synthetic.main.fragment_full_player_controls.playerMenu
import kotlinx.android.synthetic.main.fragment_full_player_controls.previousButton
import kotlinx.android.synthetic.main.fragment_full_player_controls.progressSlider
import kotlinx.android.synthetic.main.fragment_full_player_controls.repeatButton
import kotlinx.android.synthetic.main.fragment_full_player_controls.shuffleButton
import kotlinx.android.synthetic.main.fragment_full_player_controls.songCurrentProgress
import kotlinx.android.synthetic.main.fragment_full_player_controls.songFavourite
import kotlinx.android.synthetic.main.fragment_full_player_controls.songInfo
import kotlinx.android.synthetic.main.fragment_full_player_controls.songTotalTime
import kotlinx.android.synthetic.main.fragment_full_player_controls.text
import kotlinx.android.synthetic.main.fragment_full_player_controls.title
/** /**
* Created by hemanths on 20/09/17. * Created by hemanths on 20/09/17.
*/ */
class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMenuItemClickListener { class FullPlaybackControlsFragment : AbsPlayerControlsFragment(),
PopupMenu.OnMenuItemClickListener {
private var lastPlaybackControlsColor: Int = 0 private var lastPlaybackControlsColor: Int = 0
private var lastDisabledPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0
@ -91,17 +77,6 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
progressViewUpdateHelper!!.stop() progressViewUpdateHelper!!.stop()
} }
override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
animator.duration = SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
public override fun show() { public override fun show() {
playPauseButton!!.animate() playPauseButton!!.animate()
@ -228,15 +203,25 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN) previousButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
} }
override fun onUpdateProgressViews(progress: Int, total: Int) {
if (total <= 0) {
return
}
progressSlider.setRange(progress.toFloat(), total.toFloat())
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
override fun setUpProgressSlider() { override fun setUpProgressSlider() {
progressSlider!!.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { progressSlider.addOnChangeListener { _, value, fromUser ->
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) {
if (fromUser) { MusicPlayerRemote.seekTo(value.toInt())
MusicPlayerRemote.seekTo(progress) onUpdateProgressViews(
onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis) MusicPlayerRemote.songProgressMillis,
} MusicPlayerRemote.songDurationMillis
)
} }
}) }
} }
override fun onRepeatModeChanged() { override fun onRepeatModeChanged() {
@ -257,7 +242,10 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
@ -269,7 +257,10 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
@ -300,7 +291,7 @@ class FullPlaybackControlsFragment : AbsPlayerControlsFragment(), PopupMenu.OnMe
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
fun updateIsFavorite() { fun updateIsFavorite() {
if (updateIsFavoriteTask != null) { if (updateIsFavoriteTask != null) {
updateIsFavoriteTask!!.cancel(false) updateIsFavoriteTask?.cancel(false)
} }
updateIsFavoriteTask = object : AsyncTask<Song, Void, Boolean>() { updateIsFavoriteTask = object : AsyncTask<Song, Void, Boolean>() {
override fun doInBackground(vararg params: Song): Boolean? { override fun doInBackground(vararg params: Song): Boolean? {

View File

@ -1,41 +1,24 @@
package code.name.monkey.retromusic.fragments.player.material package code.name.monkey.retromusic.fragments.player.material
import android.animation.ObjectAnimator
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import kotlinx.android.synthetic.main.fragment_material_playback_controls.nextButton import kotlinx.android.synthetic.main.fragment_material_playback_controls.*
import kotlinx.android.synthetic.main.fragment_material_playback_controls.playPauseButton
import kotlinx.android.synthetic.main.fragment_material_playback_controls.previousButton
import kotlinx.android.synthetic.main.fragment_material_playback_controls.progressSlider
import kotlinx.android.synthetic.main.fragment_material_playback_controls.repeatButton
import kotlinx.android.synthetic.main.fragment_material_playback_controls.shuffleButton
import kotlinx.android.synthetic.main.fragment_material_playback_controls.songCurrentProgress
import kotlinx.android.synthetic.main.fragment_material_playback_controls.songInfo
import kotlinx.android.synthetic.main.fragment_material_playback_controls.songTotalTime
import kotlinx.android.synthetic.main.fragment_material_playback_controls.text
import kotlinx.android.synthetic.main.fragment_material_playback_controls.title
/** /**
* @author Hemanth S (h4h13). * @author Hemanth S (h4h13).
@ -115,11 +98,13 @@ class MaterialControlsFragment : AbsPlayerControlsFragment() {
override fun setDark(color: Int) { override fun setDark(color: Int) {
val colorBg = ATHUtil.resolveColor(requireContext(), R.attr.colorSurface) val colorBg = ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)
if (ColorUtil.isColorLight(colorBg)) { if (ColorUtil.isColorLight(colorBg)) {
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(requireContext(), true) lastPlaybackControlsColor =
MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
lastDisabledPlaybackControlsColor = lastDisabledPlaybackControlsColor =
MaterialValueHelper.getSecondaryDisabledTextColor(requireContext(), true) MaterialValueHelper.getSecondaryDisabledTextColor(requireContext(), true)
} else { } else {
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(requireContext(), false) lastPlaybackControlsColor =
MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
lastDisabledPlaybackControlsColor = lastDisabledPlaybackControlsColor =
MaterialValueHelper.getPrimaryDisabledTextColor(requireContext(), false) MaterialValueHelper.getPrimaryDisabledTextColor(requireContext(), false)
} }
@ -187,7 +172,10 @@ class MaterialControlsFragment : AbsPlayerControlsFragment() {
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
@ -199,7 +187,10 @@ class MaterialControlsFragment : AbsPlayerControlsFragment() {
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
@ -218,29 +209,24 @@ class MaterialControlsFragment : AbsPlayerControlsFragment() {
public override fun hide() { public override fun hide() {
} }
override fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
})
}
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider!!.max = total if (total <= 0) {
return
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) }
animator.duration = SLIDER_ANIMATION_TIME progressSlider.setRange(progress.toFloat(), total.toFloat())
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
override fun setUpProgressSlider() {
progressSlider.addOnChangeListener { _, value, fromUser ->
if (fromUser) {
MusicPlayerRemote.seekTo(value.toInt())
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
}
} }

View File

@ -1,6 +1,5 @@
package code.name.monkey.retromusic.fragments.player.normal package code.name.monkey.retromusic.fragments.player.normal
import android.animation.ObjectAnimator
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.graphics.PorterDuff import android.graphics.PorterDuff
@ -9,8 +8,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
@ -19,29 +16,20 @@ import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import kotlinx.android.synthetic.main.fragment_player_playback_controls.nextButton import kotlinx.android.synthetic.main.fragment_player_playback_controls.*
import kotlinx.android.synthetic.main.fragment_player_playback_controls.playPauseButton
import kotlinx.android.synthetic.main.fragment_player_playback_controls.previousButton
import kotlinx.android.synthetic.main.fragment_player_playback_controls.progressSlider
import kotlinx.android.synthetic.main.fragment_player_playback_controls.repeatButton
import kotlinx.android.synthetic.main.fragment_player_playback_controls.shuffleButton
import kotlinx.android.synthetic.main.fragment_player_playback_controls.songCurrentProgress
import kotlinx.android.synthetic.main.fragment_player_playback_controls.songInfo
import kotlinx.android.synthetic.main.fragment_player_playback_controls.songTotalTime
import kotlinx.android.synthetic.main.fragment_player_playback_controls.text
import kotlinx.android.synthetic.main.fragment_player_playback_controls.title
class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPreferenceChangeListener { class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(),
OnSharedPreferenceChangeListener {
private var lastPlaybackControlsColor: Int = 0 private var lastPlaybackControlsColor: Int = 0
private var lastDisabledPlaybackControlsColor: Int = 0 private var lastDisabledPlaybackControlsColor: Int = 0
@ -77,11 +65,13 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
override fun setDark(color: Int) { override fun setDark(color: Int) {
val colorBg = ATHUtil.resolveColor(requireContext(), android.R.attr.colorBackground) val colorBg = ATHUtil.resolveColor(requireContext(), android.R.attr.colorBackground)
if (ColorUtil.isColorLight(colorBg)) { if (ColorUtil.isColorLight(colorBg)) {
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(requireContext(), true) lastPlaybackControlsColor =
MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
lastDisabledPlaybackControlsColor = lastDisabledPlaybackControlsColor =
MaterialValueHelper.getSecondaryDisabledTextColor(requireContext(), true) MaterialValueHelper.getSecondaryDisabledTextColor(requireContext(), true)
} else { } else {
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(requireContext(), false) lastPlaybackControlsColor =
MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
lastDisabledPlaybackControlsColor = lastDisabledPlaybackControlsColor =
MaterialValueHelper.getPrimaryDisabledTextColor(requireContext(), false) MaterialValueHelper.getPrimaryDisabledTextColor(requireContext(), false)
} }
@ -94,15 +84,15 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
TintHelper.setTintAuto( TintHelper.setTintAuto(
playPauseButton, playPauseButton,
MaterialValueHelper.getPrimaryTextColor(requireContext(), ColorUtil.isColorLight(colorFinal)), MaterialValueHelper.getPrimaryTextColor(
requireContext(),
ColorUtil.isColorLight(colorFinal)
),
false false
) )
TintHelper.setTintAuto(playPauseButton, colorFinal, true) TintHelper.setTintAuto(playPauseButton, colorFinal, true)
ViewUtil.setProgressDrawable(progressSlider, colorFinal, true)
ViewUtil.setProgressDrawable(progressSlider, colorFinal, false)
volumeFragment?.setTintable(colorFinal) volumeFragment?.setTintable(colorFinal)
updateRepeatState() updateRepeatState()
updateShuffleState() updateShuffleState()
updatePrevNextColor() updatePrevNextColor()
@ -196,7 +186,10 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
@ -208,7 +201,10 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
@ -240,32 +236,27 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
} }
} }
override fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
})
}
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total if (total <= 0) {
return
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) }
animator.duration = SLIDER_ANIMATION_TIME progressSlider.setRange(progress.toFloat(), total.toFloat())
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
override fun setUpProgressSlider() {
progressSlider.addOnChangeListener { _, value, fromUser ->
if (fromUser) {
MusicPlayerRemote.seekTo(value.toInt())
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == PreferenceUtil.EXTRA_SONG_INFO) { if (key == PreferenceUtil.EXTRA_SONG_INFO) {
@ -275,6 +266,7 @@ class PlayerPlaybackControlsFragment : AbsPlayerControlsFragment(), OnSharedPref
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
PreferenceUtil.getInstance(requireContext()).unregisterOnSharedPreferenceChangedListener(this) PreferenceUtil.getInstance(requireContext())
.unregisterOnSharedPreferenceChangedListener(this)
} }
} }

View File

@ -14,39 +14,29 @@
package code.name.monkey.retromusic.fragments.player.peak package code.name.monkey.retromusic.fragments.player.peak
import android.animation.ObjectAnimator
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.textColorSecondary import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import kotlinx.android.synthetic.main.fragment_peak_control_player.nextButton import kotlinx.android.synthetic.main.fragment_peak_control_player.*
import kotlinx.android.synthetic.main.fragment_peak_control_player.playPauseButton
import kotlinx.android.synthetic.main.fragment_peak_control_player.previousButton
import kotlinx.android.synthetic.main.fragment_peak_control_player.progressSlider
import kotlinx.android.synthetic.main.fragment_peak_control_player.repeatButton
import kotlinx.android.synthetic.main.fragment_peak_control_player.shuffleButton
import kotlinx.android.synthetic.main.fragment_peak_control_player.songCurrentProgress
import kotlinx.android.synthetic.main.fragment_peak_control_player.songTotalTime
/** /**
* Created by hemanths on 2019-10-04. * Created by hemanths on 2019-10-04.
@ -98,11 +88,13 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
override fun setDark(color: Int) { override fun setDark(color: Int) {
val colorBg = ATHUtil.resolveColor(requireContext(), android.R.attr.colorBackground) val colorBg = ATHUtil.resolveColor(requireContext(), android.R.attr.colorBackground)
if (ColorUtil.isColorLight(colorBg)) { if (ColorUtil.isColorLight(colorBg)) {
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(requireContext(), true) lastPlaybackControlsColor =
MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
lastDisabledPlaybackControlsColor = lastDisabledPlaybackControlsColor =
MaterialValueHelper.getSecondaryDisabledTextColor(requireContext(), true) MaterialValueHelper.getSecondaryDisabledTextColor(requireContext(), true)
} else { } else {
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(requireContext(), false) lastPlaybackControlsColor =
MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
lastDisabledPlaybackControlsColor = lastDisabledPlaybackControlsColor =
MaterialValueHelper.getPrimaryDisabledTextColor(requireContext(), false) MaterialValueHelper.getPrimaryDisabledTextColor(requireContext(), false)
} }
@ -122,18 +114,6 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
updatePrevNextColor() updatePrevNextColor()
} }
override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
animator.duration = SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
private fun updatePlayPauseDrawableState() { private fun updatePlayPauseDrawableState() {
if (MusicPlayerRemote.isPlaying) { if (MusicPlayerRemote.isPlaying) {
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp) playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp)
@ -162,15 +142,25 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
} }
} }
override fun onUpdateProgressViews(progress: Int, total: Int) {
if (total <= 0) {
return
}
progressSlider.setRange(progress.toFloat(), total.toFloat())
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
override fun setUpProgressSlider() { override fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { progressSlider.addOnChangeListener { _, value, fromUser ->
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) {
if (fromUser) { MusicPlayerRemote.seekTo(value.toInt())
MusicPlayerRemote.seekTo(progress) onUpdateProgressViews(
onUpdateProgressViews(MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songDurationMillis) MusicPlayerRemote.songProgressMillis,
} MusicPlayerRemote.songDurationMillis
)
} }
}) }
} }
private fun setUpPlayPauseFab() { private fun setUpPlayPauseFab() {
@ -196,7 +186,10 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
@ -204,7 +197,10 @@ class PeakPlayerControlFragment : AbsPlayerControlsFragment() {
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)

View File

@ -1,6 +1,5 @@
package code.name.monkey.retromusic.fragments.player.plain package code.name.monkey.retromusic.fragments.player.plain
import android.animation.ObjectAnimator
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -8,8 +7,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
@ -18,12 +15,12 @@ import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.ripAlpha import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.setRange
import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
@ -86,7 +83,11 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_plain_controls_fragment, container, false) return inflater.inflate(R.layout.fragment_plain_controls_fragment, container, false)
} }
@ -142,10 +143,12 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
val colorBg = ATHUtil.resolveColor(context!!, android.R.attr.colorBackground) val colorBg = ATHUtil.resolveColor(context!!, android.R.attr.colorBackground)
if (ColorUtil.isColorLight(colorBg)) { if (ColorUtil.isColorLight(colorBg)) {
lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(context!!, true) lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(context!!, true)
lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(context!!, true) lastDisabledPlaybackControlsColor =
MaterialValueHelper.getSecondaryDisabledTextColor(context!!, true)
} else { } else {
lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(context!!, false) lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(context!!, false)
lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(context!!, false) lastDisabledPlaybackControlsColor =
MaterialValueHelper.getPrimaryDisabledTextColor(context!!, false)
} }
val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) { val colorFinal = if (PreferenceUtil.getInstance(requireContext()).adaptiveColor) {
@ -179,7 +182,10 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
@ -191,7 +197,10 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN) repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
@ -223,20 +232,6 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
} }
} }
override fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
})
}
private fun showBonceAnimation() { private fun showBonceAnimation() {
playPauseButton.apply { playPauseButton.apply {
clearAnimation() clearAnimation()
@ -269,14 +264,23 @@ class PlainPlaybackControlsFragment : AbsPlayerControlsFragment() {
} }
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total if (total <= 0) {
return
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress) }
animator.duration = SLIDER_ANIMATION_TIME progressSlider.setRange(progress.toFloat(), total.toFloat())
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
override fun setUpProgressSlider() {
progressSlider.addOnChangeListener { _, value, fromUser ->
if (fromUser) {
MusicPlayerRemote.seekTo(value.toInt())
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
}
} }

View File

@ -25,12 +25,7 @@ import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.util.ViewUtil
import kotlinx.android.synthetic.main.fragment_tiny_player.playerSongTotalTime import kotlinx.android.synthetic.main.fragment_tiny_player.*
import kotlinx.android.synthetic.main.fragment_tiny_player.playerToolbar
import kotlinx.android.synthetic.main.fragment_tiny_player.progressBar
import kotlinx.android.synthetic.main.fragment_tiny_player.songInfo
import kotlinx.android.synthetic.main.fragment_tiny_player.text
import kotlinx.android.synthetic.main.fragment_tiny_player.title
class TinyPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Callback { class TinyPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Callback {
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
@ -86,10 +81,12 @@ class TinyPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Ca
if (ColorUtil.isColorLight(colorFinal)) { if (ColorUtil.isColorLight(colorFinal)) {
textColorPrimary = MaterialValueHelper.getSecondaryTextColor(requireContext(), true) textColorPrimary = MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
textColorPrimaryDisabled = MaterialValueHelper.getSecondaryTextColor(requireContext(), true) textColorPrimaryDisabled =
MaterialValueHelper.getSecondaryTextColor(requireContext(), true)
} else { } else {
textColorPrimary = MaterialValueHelper.getPrimaryTextColor(requireContext(), false) textColorPrimary = MaterialValueHelper.getPrimaryTextColor(requireContext(), false)
textColorPrimaryDisabled = MaterialValueHelper.getSecondaryTextColor(requireContext(), false) textColorPrimaryDisabled =
MaterialValueHelper.getSecondaryTextColor(requireContext(), false)
} }
this.lastColor = colorFinal this.lastColor = colorFinal
@ -143,7 +140,11 @@ class TinyPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Ca
} }
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_tiny_player, container, false) return inflater.inflate(R.layout.fragment_tiny_player, container, false)
} }

View File

@ -17,7 +17,6 @@ package code.name.monkey.retromusic.helper
import android.os.Handler import android.os.Handler
import android.os.Message import android.os.Message
class MusicProgressViewUpdateHelper : Handler { class MusicProgressViewUpdateHelper : Handler {
private var callback: Callback? = null private var callback: Callback? = null
@ -54,8 +53,8 @@ class MusicProgressViewUpdateHelper : Handler {
private fun refreshProgressViews(): Int { private fun refreshProgressViews(): Int {
val progressMillis = MusicPlayerRemote.songProgressMillis val progressMillis = MusicPlayerRemote.songProgressMillis
val totalMillis = MusicPlayerRemote.songDurationMillis val totalMillis = MusicPlayerRemote.songDurationMillis
println("$progressMillis $totalMillis")
callback!!.onUpdateProgressViews(progressMillis, totalMillis) callback?.onUpdateProgressViews(progressMillis, totalMillis)
if (!MusicPlayerRemote.isPlaying) { if (!MusicPlayerRemote.isPlaying) {
return intervalPaused return intervalPaused

View File

@ -32,7 +32,7 @@ import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.dialogs.OptionsSheetDialogFragment; import code.name.monkey.retromusic.dialogs.OptionsSheetDialogFragment;
import code.name.monkey.retromusic.fragments.AlbumCoverStyle; import code.name.monkey.retromusic.fragments.AlbumCoverStyle;
import code.name.monkey.retromusic.fragments.NowPlayingScreen; import code.name.monkey.retromusic.fragments.NowPlayingScreen;
import code.name.monkey.retromusic.fragments.mainactivity.folders.FoldersFragment; import code.name.monkey.retromusic.fragments.mainactivity.FoldersFragment;
import code.name.monkey.retromusic.helper.SortOrder; import code.name.monkey.retromusic.helper.SortOrder;
import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder; import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder;
import code.name.monkey.retromusic.model.CategoryInfo; import code.name.monkey.retromusic.model.CategoryInfo;

View File

@ -28,6 +28,7 @@ import androidx.core.view.ViewCompat
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import com.google.android.material.slider.Slider
object ViewUtil { object ViewUtil {
@ -47,6 +48,16 @@ object ViewUtil {
} }
} }
fun setProgressDrawable(progressSlider: Slider, color: Int, thumbTint: Boolean = false) {
if (thumbTint) {
progressSlider.thumbColor = ColorStateList.valueOf(color)
}
val colorWithAlpha = ColorUtil.withAlpha(color, 0.25f)
progressSlider.haloColor = ColorStateList.valueOf(colorWithAlpha)
progressSlider.trackColorActive = ColorStateList.valueOf(color)
progressSlider.trackColorInactive = ColorStateList.valueOf(colorWithAlpha)
}
fun setProgressDrawable(progressSlider: ProgressBar, newColor: Int) { fun setProgressDrawable(progressSlider: ProgressBar, newColor: Int) {
val layerDrawable = progressSlider.progressDrawable as LayerDrawable val layerDrawable = progressSlider.progressDrawable as LayerDrawable

View File

@ -16,12 +16,11 @@ package code.name.monkey.retromusic.views
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import com.google.android.material.imageview.ExperimentalImageView
import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.shape.CornerFamily import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel import com.google.android.material.shape.ShapeAppearanceModel
@ExperimentalImageView
class RetroShapeableImageView @JvmOverloads constructor( class RetroShapeableImageView @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
@ -29,8 +28,10 @@ class RetroShapeableImageView @JvmOverloads constructor(
) : ShapeableImageView(context, attrs, defStyle) { ) : ShapeableImageView(context, attrs, defStyle) {
init { init {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RetroShapeableImageView, defStyle, -1) val typedArray =
val cornerSize = typedArray.getDimension(R.styleable.RetroShapeableImageView_retroCornerSize, 0f); context.obtainStyledAttributes(attrs, R.styleable.RetroShapeableImageView, defStyle, -1)
val cornerSize =
typedArray.getDimension(R.styleable.RetroShapeableImageView_retroCornerSize, 0f);
shapeAppearanceModel = ShapeAppearanceModel.Builder() shapeAppearanceModel = ShapeAppearanceModel.Builder()
.setAllCorners(CornerFamily.ROUNDED, cornerSize) .setAllCorners(CornerFamily.ROUNDED, cornerSize)
.build() .build()

View File

@ -23,13 +23,16 @@ import androidx.annotation.NonNull;
public class AudioVolumeContentObserver extends ContentObserver { public class AudioVolumeContentObserver extends ContentObserver {
private final OnAudioVolumeChangedListener mListener; private final OnAudioVolumeChangedListener mListener;
private final AudioManager mAudioManager; private final AudioManager mAudioManager;
private final int mAudioStreamType; private final int mAudioStreamType;
private int mLastVolume;
private float mLastVolume;
AudioVolumeContentObserver(@NonNull Handler handler, @NonNull AudioManager audioManager, AudioVolumeContentObserver(@NonNull Handler handler, @NonNull AudioManager audioManager,
int audioStreamType, int audioStreamType,
@NonNull OnAudioVolumeChangedListener listener) { @NonNull OnAudioVolumeChangedListener listener) {
super(handler); super(handler);
mAudioManager = audioManager; mAudioManager = audioManager;
@ -44,8 +47,8 @@ public class AudioVolumeContentObserver extends ContentObserver {
@Override @Override
public void onChange(boolean selfChange, Uri uri) { public void onChange(boolean selfChange, Uri uri) {
if (mAudioManager != null && mListener != null) { if (mAudioManager != null && mListener != null) {
int maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType); float maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType);
int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType); float currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);
if (currentVolume != mLastVolume) { if (currentVolume != mLastVolume) {
mLastVolume = currentVolume; mLastVolume = currentVolume;
mListener.onAudioVolumeChanged(currentVolume, maxVolume); mListener.onAudioVolumeChanged(currentVolume, maxVolume);

View File

@ -11,10 +11,8 @@
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * See the GNU General Public License for more details.
*/ */
package code.name.monkey.retromusic.volume
package code.name.monkey.retromusic.volume; interface OnAudioVolumeChangedListener {
fun onAudioVolumeChanged(currentVolume: Float, maxVolume: Float)
public interface OnAudioVolumeChangedListener {
void onAudioVolumeChanged(int currentVolume, int maxVolume);
} }

View File

@ -54,39 +54,6 @@
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black_24dp" app:navigationIcon="@drawable/ic_keyboard_arrow_down_black_24dp"
tools:layout_editor_absoluteY="24dp" /> tools:layout_editor_absoluteY="24dp" />
<LinearLayout
android:id="@+id/titleContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@+id/playerToolbar">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="1"
android:textAppearance="@style/TextViewHeadline4"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold"
tools:text="@tools:sample/lorem/random" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="2"
android:textAppearance="@style/TextViewBody1"
app:layout_constraintBottom_toTopOf="@+id/progressContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@+id/titleContainer"
tools:text="@tools:sample/lorem/random" />
<code.name.monkey.retromusic.views.SeekArc <code.name.monkey.retromusic.views.SeekArc
android:id="@+id/volumeSeekBar" android:id="@+id/volumeSeekBar"
@ -98,7 +65,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/guideline" app:layout_constraintEnd_toEndOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toBottomOf="@id/playerToolbar"
app:progressWidth="4dp" app:progressWidth="4dp"
app:rotation="180" app:rotation="180"
app:roundEdges="true" app:roundEdges="true"
@ -152,57 +119,88 @@
app:layout_constraintEnd_toEndOf="@+id/volumeSeekBar" app:layout_constraintEnd_toEndOf="@+id/volumeSeekBar"
app:layout_constraintStart_toStartOf="@+id/volumeSeekBar" /> app:layout_constraintStart_toStartOf="@+id/volumeSeekBar" />
<RelativeLayout <LinearLayout
android:id="@+id/progressContainer" android:id="@+id/titleContainer"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="28dp" android:layout_height="wrap_content"
android:paddingStart="12dp" app:layout_constraintBottom_toTopOf="@+id/text"
android:paddingEnd="12dp"
app:layout_constraintBottom_toTopOf="@+id/songInfo"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@+id/text"> app:layout_constraintTop_toBottomOf="@+id/playerToolbar">
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.textview.MaterialTextView
android:id="@+id/progressSlider" android:id="@+id/title"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toLeftOf="@id/songTotalTime" android:gravity="center"
android:layout_toRightOf="@id/songCurrentProgress" android:maxLines="1"
android:maxHeight="3dp" android:paddingStart="0dp"
android:progressDrawable="@drawable/color_progress_seek" android:paddingEnd="16dp"
android:splitTrack="false" android:textAppearance="@style/TextViewHeadline4"
android:thumb="@drawable/switch_thumb_material" android:textColor="?android:attr/textColorPrimary"
tools:ignore="RtlHardcoded,UnusedAttribute" android:textStyle="bold"
tools:progress="20" /> tools:text="@tools:sample/lorem/random" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/songTotalTime" android:id="@+id/text"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:gravity="center"
android:gravity="center_vertical|right|end" android:maxLines="2"
android:paddingRight="8dp" android:paddingStart="0dp"
android:singleLine="true" android:paddingEnd="16dp"
android:textColor="?android:attr/textColorSecondary" android:textAppearance="@style/TextViewBody1"
android:textSize="12sp" app:layout_constraintEnd_toEndOf="parent"
tools:ignore="RtlHardcoded,RtlSymmetry" app:layout_constraintBottom_toTopOf="@id/progressSlider"
tools:text="00:22" /> app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@+id/titleContainer"
tools:text="@tools:sample/lorem/random" />
<com.google.android.material.slider.Slider
android:id="@+id/progressSlider"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:labelBehavior="gone"
app:layout_constraintBottom_toTopOf="@id/songInfo"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toBottomOf="@id/text"
app:thumbColor="?attr/colorSurface"
app:trackColorInactive="?attr/colorControlNormal"
tools:ignore="RtlHardcoded,UnusedAttribute" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/songTotalTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical|right|end"
android:paddingRight="8dp"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/progressSlider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/songCurrentProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:gravity="center_vertical|left|end"
android:paddingLeft="8dp"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/progressSlider"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/songCurrentProgress"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:gravity="center_vertical|left|end"
android:paddingLeft="8dp"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" />
</RelativeLayout>
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/songInfo" android:id="@+id/songInfo"
@ -220,6 +218,5 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline" app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@+id/progressContainer"
tools:text="@tools:sample/lorem" /> tools:text="@tools:sample/lorem" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -128,17 +128,14 @@
app:layout_constraintStart_toEndOf="@+id/nextButton" app:layout_constraintStart_toEndOf="@+id/nextButton"
app:layout_constraintTop_toTopOf="@+id/playPauseButton"> app:layout_constraintTop_toTopOf="@+id/playPauseButton">
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toLeftOf="@id/songTotalTime" android:layout_toLeftOf="@id/songTotalTime"
android:layout_toRightOf="@id/songCurrentProgress" android:layout_toRightOf="@id/songCurrentProgress"
android:maxHeight="3dp" app:labelBehavior="gone"
android:progressDrawable="@drawable/color_progress_seek" app:thumbColor="?attr/colorSurface"
android:splitTrack="false"
android:thumb="@drawable/switch_thumb_material"
tools:ignore="RtlHardcoded,UnusedAttribute" tools:ignore="RtlHardcoded,UnusedAttribute"
tools:progress="20" /> tools:progress="20" />

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/playbackControls"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/playbackControls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
@ -18,28 +18,27 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" /> tools:text="00:22" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_weight="8" android:value="1.0f"
android:maxHeight="1.5dp" android:valueFrom="0.0f"
android:progressDrawable="@drawable/color_progress_seek" android:valueTo="1.0f"
android:splitTrack="false" app:labelBehavior="gone"
app:layout_constraintEnd_toStartOf="@id/songTotalTime" app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress" app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded,UnusedAttribute" app:trackHeight="3dp"
tools:progress="20" /> tools:valueFrom="0.0"
tools:valueTo="11.0" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/songTotalTime" android:id="@+id/songTotalTime"
@ -50,7 +49,6 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"
@ -138,8 +136,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" android:layout_weight="0"
android:paddingStart="8dp"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingStart="8dp"
app:layout_constraintBottom_toTopOf="@id/songInfo" app:layout_constraintBottom_toTopOf="@id/songInfo"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -155,9 +153,9 @@
android:ellipsize="end" android:ellipsize="end"
android:gravity="center" android:gravity="center"
android:maxLines="1" android:maxLines="1"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@ -31,27 +31,34 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" /> tools:text="00:22" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="8" android:layout_weight="8"
android:maxHeight="1.5dp"
android:progressDrawable="@drawable/color_progress_seek"
android:splitTrack="false" android:splitTrack="false"
android:value="1.0f"
android:valueFrom="0.0f"
android:valueTo="1.0f"
app:haloColor="@color/md_white_semi_transparent"
app:labelBehavior="gone"
app:layout_constraintEnd_toStartOf="@id/songTotalTime" app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress" app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:thumbColor="@color/md_white_1000"
app:trackColorActive="@color/md_white_1000"
app:trackColorInactive="@color/md_white_semi_transparent"
app:trackHeight="3dp"
tools:ignore="RtlHardcoded,UnusedAttribute" tools:ignore="RtlHardcoded,UnusedAttribute"
tools:progress="20" /> tools:progress="20"
tools:valueFrom="0.0"
tools:valueTo="11.0" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/songTotalTime" android:id="@+id/songTotalTime"
@ -62,7 +69,6 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout 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" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/playback_controls" android:id="@+id/playback_controls"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -53,18 +54,18 @@
tools:text="22.00" /> tools:text="22.00" />
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxHeight="2dp"
android:paddingStart="20dp" android:paddingStart="20dp"
android:paddingEnd="20dp" android:paddingEnd="20dp"
android:progressDrawable="@drawable/color_progress_seek" app:haloColor="@color/md_white_semi_transparent"
android:progressTint="@color/md_white_1000" app:labelBehavior="gone"
android:splitTrack="false" app:thumbColor="@color/md_white_1000"
android:thumbTint="@color/md_white_1000" app:trackColorActive="@color/md_white_1000"
app:trackColorInactive="@color/md_white_semi_transparent"
app:trackHeight="2dp"
tools:progress="20" /> tools:progress="20" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView

View File

@ -62,8 +62,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:maxLines="2" android:maxLines="2"
android:paddingStart="8dp" android:paddingStart="16dp"
android:paddingEnd="8dp" android:paddingEnd="16dp"
android:textAppearance="@style/TextViewHeadline5" android:textAppearance="@style/TextViewHeadline5"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold" android:textStyle="bold"
@ -76,6 +76,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:maxLines="2" android:maxLines="2"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAppearance="@style/TextViewBody1" android:textAppearance="@style/TextViewBody1"
app:layout_constraintBottom_toTopOf="@+id/volumeSeekBar" app:layout_constraintBottom_toTopOf="@+id/volumeSeekBar"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -147,55 +149,48 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/volumeSeekBar" /> app:layout_constraintStart_toStartOf="@+id/volumeSeekBar" />
<RelativeLayout
android:id="@+id/progressContainer" <com.google.android.material.slider.Slider
android:layout_width="match_parent" android:id="@+id/progressSlider"
android:layout_height="28dp" android:layout_width="0dp"
android:paddingStart="12dp" android:layout_height="wrap_content"
android:paddingEnd="12dp" app:labelBehavior="gone"
app:layout_constraintBottom_toTopOf="@+id/songInfo" app:layout_constraintBottom_toTopOf="@+id/songInfo"
app:layout_constraintTop_toBottomOf="@+id/volumeSeekBar"> app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toBottomOf="@+id/volumeSeekBar"
app:thumbColor="?attr/colorSurface"
app:trackColorInactive="?attr/colorControlNormal"
tools:ignore="RtlHardcoded,UnusedAttribute"
tools:progress="20" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.textview.MaterialTextView
android:id="@+id/progressSlider" android:id="@+id/songCurrentProgress"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:gravity="center_vertical|left|end"
android:layout_toLeftOf="@id/songTotalTime" android:paddingLeft="16dp"
android:layout_toRightOf="@id/songCurrentProgress" android:singleLine="true"
android:maxHeight="3dp" android:textColor="?android:attr/textColorSecondary"
android:progressDrawable="@drawable/color_progress_seek" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
android:splitTrack="false" app:layout_constraintStart_toStartOf="parent"
android:thumb="@drawable/switch_thumb_material" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,UnusedAttribute" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:progress="20" /> tools:text="00:22" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/songTotalTime" android:id="@+id/songTotalTime"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:gravity="center_vertical|right|end"
android:gravity="center_vertical|right|end" android:paddingRight="16dp"
android:paddingRight="8dp" android:singleLine="true"
android:singleLine="true" android:textColor="?android:attr/textColorSecondary"
android:textColor="?android:attr/textColorSecondary" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
android:textSize="12sp" app:layout_constraintEnd_toEndOf="parent"
tools:ignore="RtlHardcoded,RtlSymmetry" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:text="00:22" /> tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/songCurrentProgress"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:gravity="center_vertical|left|end"
android:paddingLeft="8dp"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" />
</RelativeLayout>
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/songInfo" android:id="@+id/songInfo"

View File

@ -17,23 +17,19 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" /> tools:text="00:22" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_weight="8" app:labelBehavior="gone"
android:maxHeight="2dp" app:trackHeight="3dp"
android:progressDrawable="@drawable/color_progress_seek"
android:splitTrack="false"
app:layout_constraintEnd_toStartOf="@id/songTotalTime" app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress" app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -49,7 +45,6 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"

View File

@ -17,23 +17,26 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" /> tools:text="00:22" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="8" android:layout_weight="8"
android:splitTrack="false" android:splitTrack="false"
android:thumb="@drawable/switch_square" android:thumb="@drawable/switch_square"
app:labelBehavior="gone"
app:haloRadius="0dp"
app:layout_constraintEnd_toStartOf="@id/songTotalTime" app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress" app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:thumbRadius="8dp"
app:trackHeight="3dp"
tools:ignore="RtlHardcoded,UnusedAttribute" tools:ignore="RtlHardcoded,UnusedAttribute"
tools:progress="20" /> tools:progress="20" />
@ -46,7 +49,6 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"

View File

@ -83,19 +83,20 @@
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" /> tools:text="00:22" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_weight="8" app:haloColor="@color/md_white_semi_transparent"
android:maxHeight="3dp" app:labelBehavior="gone"
android:progressDrawable="@drawable/color_progress_seek"
android:splitTrack="false"
app:layout_constraintEnd_toStartOf="@id/songTotalTime" app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress" app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toBottomOf="@id/text" app:layout_constraintTop_toBottomOf="@id/text"
app:thumbColor="@color/md_white_1000"
app:trackColorActive="@color/md_white_1000"
app:trackColorInactive="@color/md_white_semi_transparent"
app:trackHeight="3dp"
tools:ignore="RtlHardcoded,UnusedAttribute" tools:ignore="RtlHardcoded,UnusedAttribute"
tools:progress="20" /> tools:progress="20" />

View File

@ -18,26 +18,22 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" /> tools:text="00:22" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_weight="8" app:labelBehavior="gone"
android:maxHeight="3dp"
android:progressDrawable="@drawable/color_progress_seek"
android:splitTrack="false"
app:layout_constraintEnd_toStartOf="@id/songTotalTime" app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress" app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:trackHeight="3dp"
tools:ignore="RtlHardcoded,UnusedAttribute" tools:ignore="RtlHardcoded,UnusedAttribute"
tools:progress="20" /> tools:progress="20" />
@ -50,7 +46,6 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"

View File

@ -28,23 +28,19 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" /> tools:text="00:22" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_weight="8" app:labelBehavior="gone"
android:maxHeight="3dp" app:trackHeight="3dp"
android:progressDrawable="@drawable/color_progress_seek"
android:splitTrack="false"
app:layout_constraintEnd_toStartOf="@id/songTotalTime" app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress" app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -60,7 +56,6 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"

View File

@ -16,26 +16,22 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" /> tools:text="00:22" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="8" app:labelBehavior="gone"
android:maxHeight="3dp"
android:progressDrawable="@drawable/color_progress_seek"
android:splitTrack="false"
app:layout_constraintBottom_toTopOf="@+id/playPauseButton" app:layout_constraintBottom_toTopOf="@+id/playPauseButton"
app:layout_constraintEnd_toStartOf="@id/songTotalTime" app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress" app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:trackHeight="3dp"
tools:ignore="RtlHardcoded,UnusedAttribute" tools:ignore="RtlHardcoded,UnusedAttribute"
tools:progress="20" /> tools:progress="20" />
@ -48,7 +44,6 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/progressSlider" app:layout_constraintTop_toTopOf="@+id/progressSlider"

View File

@ -18,26 +18,21 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/progressSlider" app:layout_constraintTop_toTopOf="@id/progressSlider"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:text="00:22" /> tools:text="00:22" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/progressSlider" android:id="@+id/progressSlider"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxHeight="3dp" app:labelBehavior="gone"
android:padding="0dp"
android:progressDrawable="@drawable/color_progress_seek"
android:splitTrack="false"
android:thumb="@drawable/switch_thumb_material"
app:layout_constraintEnd_toStartOf="@id/songTotalTime" app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress" app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:trackHeight="3dp"
tools:ignore="RtlHardcoded,UnusedAttribute" tools:ignore="RtlHardcoded,UnusedAttribute"
tools:progress="20" /> tools:progress="20" />
@ -50,7 +45,6 @@
android:minWidth="40dp" android:minWidth="40dp"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/progressSlider" app:layout_constraintBottom_toBottomOf="@+id/progressSlider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"

View File

@ -45,7 +45,6 @@
<include layout="@layout/status_bar" /> <include layout="@layout/status_bar" />
</FrameLayout> </FrameLayout>
<code.name.monkey.retromusic.views.VerticalTextView <code.name.monkey.retromusic.views.VerticalTextView
android:id="@+id/playerSongTotalTime" android:id="@+id/playerSongTotalTime"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/viewGroup" android:id="@+id/viewGroup"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -25,22 +24,21 @@
app:srcCompat="@drawable/ic_volume_down_white_24dp" app:srcCompat="@drawable/ic_volume_down_white_24dp"
app:tint="?attr/colorControlNormal" /> app:tint="?attr/colorControlNormal" />
<androidx.appcompat.widget.AppCompatSeekBar <com.google.android.material.slider.Slider
android:id="@+id/volumeSeekBar" android:id="@+id/volumeSeekBar"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:max="100"
android:maxHeight="2dp" android:maxHeight="2dp"
android:progressDrawable="@drawable/color_progress_seek" android:progressDrawable="@drawable/color_progress_seek"
app:labelBehavior="gone"
app:layout_constraintBottom_toBottomOf="@+id/volumeDown" app:layout_constraintBottom_toBottomOf="@+id/volumeDown"
app:layout_constraintEnd_toStartOf="@+id/volumeUp" app:layout_constraintEnd_toStartOf="@+id/volumeUp"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/volumeDown" app:layout_constraintStart_toEndOf="@+id/volumeDown"
app:layout_constraintTop_toTopOf="@+id/volumeDown" app:layout_constraintTop_toTopOf="@+id/volumeDown"
tools:progress="20" app:thumbRadius="8dp"
tools:progressTint="?attr/colorControlNormal" /> app:trackHeight="2dp" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/volumeUp" android:id="@+id/volumeUp"

View File

@ -28,7 +28,7 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':fonts') implementation project(':fonts')
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha04' implementation 'com.google.android.material:material:1.2.0-alpha05'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
// Used for the list preference classes // Used for the list preference classes

View File

@ -47,6 +47,7 @@
<color name="md_black_1000">#000000</color> <color name="md_black_1000">#000000</color>
<color name="md_white_1000">#FFFFFF</color> <color name="md_white_1000">#FFFFFF</color>
<color name="md_white_semi_transparent">#40FFFFFF</color>
<color name="md_teal_A400">#1DE9B6</color> <color name="md_teal_A400">#1DE9B6</color>

View File

@ -3,10 +3,8 @@ org.gradle.daemon=true
org.gradle.parallel=true org.gradle.parallel=true
jvmArgs='-Xmx2048m' jvmArgs='-Xmx2048m'
android.useAndroidX=true android.useAndroidX=true
android.enabelR8=true
android.enableR8.fullMode=false android.enableR8.fullMode=false
android.enableJetifier=true android.enableJetifier=true
android.debug.obsoleteApi=true android.debug.obsoleteApi=true
android.enableBuildCache=true android.enableBuildCache=true
android.jetifier.blacklist = butterknife.*\\.jar
kotlin.code.style=official kotlin.code.style=official

View File

@ -1,6 +1,6 @@
#Fri Sep 20 00:21:23 IST 2019 #Mon Feb 24 22:35:41 IST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

View File

@ -1 +0,0 @@
/build

View File

@ -1,49 +0,0 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha01'
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:1.9.5"
testCompile "org.robolectric:robolectric:3.0"
}
// Running from Gradle tab in IDE would create liboverscroll/build/lib/liboverscroll-sources.jar
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
failOnError false
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
// Running from Gradle tab in IDE would create liboverscroll/build/lib/liboverscroll-javadoc.jar
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="me.everything" >
</manifest>

View File

@ -1,99 +0,0 @@
package me.everything.android.ui.overscroll;
import android.view.MotionEvent;
import android.view.View;
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
/**
* A concrete implementation of {@link OverScrollBounceEffectDecoratorBase} for a horizontal orientation.
*
* @author amit
*/
public class HorizontalOverScrollBounceEffectDecorator extends OverScrollBounceEffectDecoratorBase {
protected static class MotionAttributesHorizontal extends MotionAttributes {
public boolean init(View view, MotionEvent event) {
// We must have history available to calc the dx. Normally it's there - if it isn't temporarily,
// we declare the event 'invalid' and expect it in consequent events.
if (event.getHistorySize() == 0) {
return false;
}
// Allow for counter-orientation-direction operations (e.g. item swiping) to run fluently.
final float dy = event.getY(0) - event.getHistoricalY(0, 0);
final float dx = event.getX(0) - event.getHistoricalX(0, 0);
if (Math.abs(dx) < Math.abs(dy)) {
return false;
}
mAbsOffset = view.getTranslationX();
mDeltaOffset = dx;
mDir = mDeltaOffset > 0;
return true;
}
}
protected static class AnimationAttributesHorizontal extends AnimationAttributes {
public AnimationAttributesHorizontal() {
mProperty = View.TRANSLATION_X;
}
@Override
protected void init(View view) {
mAbsOffset = view.getTranslationX();
mMaxOffset = view.getWidth();
}
}
/**
* C'tor, creating the effect with default arguments:
* <br/>Touch-drag ratio in 'forward' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD.
* <br/>Touch-drag ratio in 'backwards' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK.
* <br/>Deceleration factor (for the bounce-back effect) will be set to DEFAULT_DECELERATE_FACTOR.
*
* @param viewAdapter The view's encapsulation.
*/
public HorizontalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter) {
this(viewAdapter, DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD, DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK, DEFAULT_DECELERATE_FACTOR);
}
/**
* C'tor, creating the effect with explicit arguments.
* @param viewAdapter The view's encapsulation.
* @param touchDragRatioFwd Ratio of touch distance to actual drag distance when in 'forward' direction.
* @param touchDragRatioBck Ratio of touch distance to actual drag distance when in 'backward'
* direction (opposite to initial one).
* @param decelerateFactor Deceleration factor used when decelerating the motion to create the
* bounce-back effect.
*/
public HorizontalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter,
float touchDragRatioFwd, float touchDragRatioBck, float decelerateFactor) {
super(viewAdapter, decelerateFactor, touchDragRatioFwd, touchDragRatioBck);
}
@Override
protected MotionAttributes createMotionAttributes() {
return new MotionAttributesHorizontal();
}
@Override
protected AnimationAttributes createAnimationAttributes() {
return new AnimationAttributesHorizontal();
}
@Override
protected void translateView(View view, float offset) {
view.setTranslationX(offset);
}
@Override
protected void translateViewAndEvent(View view, float offset, MotionEvent event) {
view.setTranslationX(offset);
event.offsetLocation(offset - event.getX(0), 0f);
}
}

View File

@ -1,33 +0,0 @@
package me.everything.android.ui.overscroll;
import android.view.View;
/**
* @author amit
*/
public interface IOverScrollDecor {
View getView();
void setOverScrollStateListener(IOverScrollStateListener listener);
void setOverScrollUpdateListener(IOverScrollUpdateListener listener);
/**
* Get the current decorator's runtime state, i.e. one of the values specified by {@link IOverScrollState}.
* @return The state.
*/
int getCurrentState();
/**
* Detach the decorator from its associated view, thus disabling it entirely.
*
* <p>It is best to call this only when over-scroll isn't currently in-effect - i.e. verify that
* <code>getCurrentState()==IOverScrollState.STATE_IDLE</code> as a precondition, or otherwise
* use a state listener previously installed using
* {@link #setOverScrollStateListener(IOverScrollStateListener)}.</p>
*
* <p>Note: Upon detachment completion, the view in question will return to the default
* Android over-scroll configuration (i.e. {@link View.OVER_SCROLL_ALWAYS} mode). This can be
* overridden by calling <code>View.setOverScrollMode(mode)</code> immediately thereafter.</p>
*/
void detach();
}

View File

@ -1,19 +0,0 @@
package me.everything.android.ui.overscroll;
/**
* @author amit
*/
public interface IOverScrollState {
/** No over-scroll is in-effect. */
int STATE_IDLE = 0;
/** User is actively touch-dragging, thus enabling over-scroll at the view's <i>start</i> side. */
int STATE_DRAG_START_SIDE = 1;
/** User is actively touch-dragging, thus enabling over-scroll at the view's <i>end</i> side. */
int STATE_DRAG_END_SIDE = 2;
/** User has released their touch, thus throwing the view back into place via bounce-back animation. */
int STATE_BOUNCE_BACK = 3;
}

View File

@ -1,25 +0,0 @@
package me.everything.android.ui.overscroll;
/**
* A callback-listener enabling over-scroll effect clients to be notified of effect state transitions.
* <br/>Invoked whenever state is transitioned onto one of {@link IOverScrollState#STATE_IDLE},
* {@link IOverScrollState#STATE_DRAG_START_SIDE}, {@link IOverScrollState#STATE_DRAG_END_SIDE}
* or {@link IOverScrollState#STATE_BOUNCE_BACK}.
*
* @author amit
*
* @see IOverScrollUpdateListener
*/
public interface IOverScrollStateListener {
/**
* The invoked callback.
*
* @param decor The associated over-scroll 'decorator'.
* @param oldState The old over-scroll state; ID's specified by {@link IOverScrollState}, e.g.
* {@link IOverScrollState#STATE_IDLE}.
* @param newState The <b>new</b> over-scroll state; ID's specified by {@link IOverScrollState},
* e.g. {@link IOverScrollState#STATE_IDLE}.
*/
void onOverScrollStateChange(IOverScrollDecor decor, int oldState, int newState);
}

View File

@ -1,22 +0,0 @@
package me.everything.android.ui.overscroll;
/**
* A callback-listener enabling over-scroll effect clients to subscribe to <b>real-time</b> updates
* of over-scrolling intensity, provided as the view-translation offset from pre-scroll position.
*
* @author amit
*
* @see IOverScrollStateListener
*/
public interface IOverScrollUpdateListener {
/**
* The invoked callback.
*
* @param decor The associated over-scroll 'decorator'.
* @param state One of: {@link IOverScrollState#STATE_IDLE}, {@link IOverScrollState#STATE_DRAG_START_SIDE},
* {@link IOverScrollState#STATE_DRAG_START_SIDE} or {@link IOverScrollState#STATE_BOUNCE_BACK}.
* @param offset The currently visible offset created due to over-scroll.
*/
void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset);
}

View File

@ -1,17 +0,0 @@
package me.everything.android.ui.overscroll;
/**
* @author amit
*/
public interface ListenerStubs {
class OverScrollStateListenerStub implements IOverScrollStateListener {
@Override
public void onOverScrollStateChange(IOverScrollDecor decor, int oldState, int newState) { }
}
class OverScrollUpdateListenerStub implements IOverScrollUpdateListener {
@Override
public void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset) { }
}
}

View File

@ -1,483 +0,0 @@
package me.everything.android.ui.overscroll;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.util.Property;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
import me.everything.android.ui.overscroll.adapters.RecyclerViewOverScrollDecorAdapter;
import static me.everything.android.ui.overscroll.IOverScrollState.*;
import static me.everything.android.ui.overscroll.ListenerStubs.*;
/**
* A standalone view decorator adding over-scroll with a smooth bounce-back effect to (potentially) any view -
* provided that an appropriate {@link IOverScrollDecoratorAdapter} implementation exists / can be written
* for that view type (e.g. {@link RecyclerViewOverScrollDecorAdapter}).
*
* <p>Design-wise, being a standalone class, this decorator powerfully provides the ability to add
* the over-scroll effect over any view without adjusting the view's implementation. In essence, this
* eliminates the need to repeatedly implement the effect per each view type (list-view,
* recycler-view, image-view, etc.). Therefore, using it is highly recommended compared to other
* more intrusive solutions.</p>
*
* <p>Note that this class is abstract, having {@link HorizontalOverScrollBounceEffectDecorator} and
* {@link VerticalOverScrollBounceEffectDecorator} providing concrete implementations that are
* view-orientation specific.</p>
*
* <hr width="97%"/>
* <h2>Implementation Notes</h2>
*
* <p>At it's core, the class simply registers itself as a touch-listener over the decorated view and
* intercepts touch events as needed.</p>
*
* <p>Internally, it delegates the over-scrolling calculations onto 3 state-based classes:
* <ol>
* <li><b>Idle state</b> - monitors view state and touch events to intercept over-scrolling initiation
* (in which case it hands control over to the Over-scrolling state).</li>
* <li><b>Over-scrolling state</b> - handles motion events to apply the over-scroll effect as users
* interact with the view.</li>
* <li><b>Bounce-back state</b> - runs the bounce-back animation, all-the-while blocking all
* touch events till the animation completes (in which case it hands control back to the idle
* state).</li>
* </ol>
* </p>
*
* @author amit
*
* @see RecyclerViewOverScrollDecorAdapter
* @see IOverScrollDecoratorAdapter
*/
public abstract class OverScrollBounceEffectDecoratorBase implements IOverScrollDecor, View.OnTouchListener {
public static final String TAG = "OverScrollDecor";
public static final float DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD = 3f;
public static final float DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK = 1f;
public static final float DEFAULT_DECELERATE_FACTOR = -2f;
protected static final int MAX_BOUNCE_BACK_DURATION_MS = 800;
protected static final int MIN_BOUNCE_BACK_DURATION_MS = 200;
protected final OverScrollStartAttributes mStartAttr = new OverScrollStartAttributes();
protected final IOverScrollDecoratorAdapter mViewAdapter;
protected final IdleState mIdleState;
protected final OverScrollingState mOverScrollingState;
protected final BounceBackState mBounceBackState;
protected IDecoratorState mCurrentState;
protected IOverScrollStateListener mStateListener = new OverScrollStateListenerStub();
protected IOverScrollUpdateListener mUpdateListener = new OverScrollUpdateListenerStub();
/**
* When in over-scroll mode, keep track of dragging velocity to provide a smooth slow-down
* for the bounce-back effect.
*/
protected float mVelocity;
/**
* Motion attributes: keeps data describing current motion event.
* <br/>Orientation agnostic: subclasses provide either horizontal or vertical
* initialization of the agnostic attributes.
*/
protected abstract static class MotionAttributes {
public float mAbsOffset;
public float mDeltaOffset;
public boolean mDir; // True = 'forward', false = 'backwards'.
protected abstract boolean init(View view, MotionEvent event);
}
protected static class OverScrollStartAttributes {
protected int mPointerId;
protected float mAbsOffset;
protected boolean mDir; // True = 'forward', false = 'backwards'.
}
protected abstract static class AnimationAttributes {
public Property<View, Float> mProperty;
public float mAbsOffset;
public float mMaxOffset;
protected abstract void init(View view);
}
/**
* Interface of decorator-state delegation classes. Defines states as handles of two fundamental
* touch events: actual movement, up/cancel.
*/
protected interface IDecoratorState {
/**
* Handle a motion (touch) event.
*
* @param event The event from onTouch.
* @return Return value for onTouch.
*/
boolean handleMoveTouchEvent(MotionEvent event);
/**
* Handle up / touch-cancel events.
*
* @param event The event from onTouch.
* @return Return value for onTouch.
*/
boolean handleUpOrCancelTouchEvent(MotionEvent event);
/**
* Handle a transition onto this state, as it becomes 'current' state.
* @param fromState
*/
void handleEntryTransition(IDecoratorState fromState);
/**
* The client-perspective ID of the state associated with this (internal) one. ID's
* are as specified in {@link IOverScrollState}.
*
* @return The ID, e.g. {@link IOverScrollState#STATE_IDLE}.
*/
int getStateId();
}
/**
* Idle state: monitors move events, trying to figure out whether over-scrolling should be
* initiated (i.e. when scrolled further when the view is at one of its displayable ends).
* <br/>When such is the case, it hands over control to the over-scrolling state.
*/
protected class IdleState implements IDecoratorState {
final MotionAttributes mMoveAttr;
public IdleState() {
mMoveAttr = createMotionAttributes();
}
@Override
public int getStateId() {
return STATE_IDLE;
}
@Override
public boolean handleMoveTouchEvent(MotionEvent event) {
final View view = mViewAdapter.getView();
if (!mMoveAttr.init(view, event)) {
return false;
}
// Has over-scrolling officially started?
if ((mViewAdapter.isInAbsoluteStart() && mMoveAttr.mDir) ||
(mViewAdapter.isInAbsoluteEnd() && !mMoveAttr.mDir)) {
// Save initial over-scroll attributes for future reference.
mStartAttr.mPointerId = event.getPointerId(0);
mStartAttr.mAbsOffset = mMoveAttr.mAbsOffset;
mStartAttr.mDir = mMoveAttr.mDir;
issueStateTransition(mOverScrollingState);
return mOverScrollingState.handleMoveTouchEvent(event);
}
return false;
}
@Override
public boolean handleUpOrCancelTouchEvent(MotionEvent event) {
return false;
}
@Override
public void handleEntryTransition(IDecoratorState fromState) {
mStateListener.onOverScrollStateChange(OverScrollBounceEffectDecoratorBase.this, fromState.getStateId(), this.getStateId());
}
}
/**
* Handles the actual over-scrolling: thus translating the view according to configuration
* and user interactions, dynamically.
*
* <br/><br/>The state is exited - thus completing over-scroll handling, in one of two cases:
* <br/>When user lets go of the view, it transitions control to the bounce-back state.
* <br/>When user moves the view back onto a potential 'under-scroll' state, it abruptly
* transitions control to the idle-state, so as to return touch-events management to the
* normal over-scroll-less environment (thus preventing under-scrolling and potentially regaining
* regular scrolling).
*/
protected class OverScrollingState implements IDecoratorState {
protected final float mTouchDragRatioFwd;
protected final float mTouchDragRatioBck;
final MotionAttributes mMoveAttr;
int mCurrDragState;
public OverScrollingState(float touchDragRatioFwd, float touchDragRatioBck) {
mMoveAttr = createMotionAttributes();
mTouchDragRatioFwd = touchDragRatioFwd;
mTouchDragRatioBck = touchDragRatioBck;
}
@Override
public int getStateId() {
// This is really a single class that implements 2 states, so our ID depends on what
// it was during the last invocation.
return mCurrDragState;
}
@Override
public boolean handleMoveTouchEvent(MotionEvent event) {
// Switching 'pointers' (e.g. fingers) on-the-fly isn't supported -- abort over-scroll
// smoothly using the default bounce-back animation in this case.
if (mStartAttr.mPointerId != event.getPointerId(0)) {
issueStateTransition(mBounceBackState);
return true;
}
final View view = mViewAdapter.getView();
if (!mMoveAttr.init(view, event)) {
// Keep intercepting the touch event as long as we're still over-scrolling...
return true;
}
float deltaOffset = mMoveAttr.mDeltaOffset / (mMoveAttr.mDir == mStartAttr.mDir ? mTouchDragRatioFwd : mTouchDragRatioBck);
float newOffset = mMoveAttr.mAbsOffset + deltaOffset;
// If moved in counter direction onto a potential under-scroll state -- don't. Instead, abort
// over-scrolling abruptly, thus returning control to which-ever touch handlers there
// are waiting (e.g. regular scroller handlers).
if ( (mStartAttr.mDir && !mMoveAttr.mDir && (newOffset <= mStartAttr.mAbsOffset)) ||
(!mStartAttr.mDir && mMoveAttr.mDir && (newOffset >= mStartAttr.mAbsOffset)) ) {
translateViewAndEvent(view, mStartAttr.mAbsOffset, event);
mUpdateListener.onOverScrollUpdate(OverScrollBounceEffectDecoratorBase.this, mCurrDragState, 0);
issueStateTransition(mIdleState);
return true;
}
if (view.getParent() != null) {
view.getParent().requestDisallowInterceptTouchEvent(true);
}
long dt = event.getEventTime() - event.getHistoricalEventTime(0);
if (dt > 0) { // Sometimes (though rarely) dt==0 cause originally timing is in nanos, but is presented in millis.
mVelocity = deltaOffset / dt;
}
translateView(view, newOffset);
mUpdateListener.onOverScrollUpdate(OverScrollBounceEffectDecoratorBase.this, mCurrDragState, newOffset);
return true;
}
@Override
public boolean handleUpOrCancelTouchEvent(MotionEvent event) {
issueStateTransition(mBounceBackState);
return false;
}
@Override
public void handleEntryTransition(IDecoratorState fromState) {
mCurrDragState = (mStartAttr.mDir ? STATE_DRAG_START_SIDE : STATE_DRAG_END_SIDE);
mStateListener.onOverScrollStateChange(OverScrollBounceEffectDecoratorBase.this, fromState.getStateId(), this.getStateId());
}
}
/**
* When entered, starts the bounce-back animation.
* <br/>Upon animation completion, transitions control onto the idle state; Does so by
* registering itself as an animation listener.
* <br/>In the meantime, blocks (intercepts) all touch events.
*/
protected class BounceBackState implements IDecoratorState, Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
protected final Interpolator mBounceBackInterpolator = new DecelerateInterpolator();
protected final float mDecelerateFactor;
protected final float mDoubleDecelerateFactor;
protected final AnimationAttributes mAnimAttributes;
public BounceBackState(float decelerateFactor) {
mDecelerateFactor = decelerateFactor;
mDoubleDecelerateFactor = 2f * decelerateFactor;
mAnimAttributes = createAnimationAttributes();
}
@Override
public int getStateId() {
return STATE_BOUNCE_BACK;
}
@Override
public void handleEntryTransition(IDecoratorState fromState) {
mStateListener.onOverScrollStateChange(OverScrollBounceEffectDecoratorBase.this, fromState.getStateId(), this.getStateId());
Animator bounceBackAnim = createAnimator();
bounceBackAnim.addListener(this);
bounceBackAnim.start();
}
@Override
public boolean handleMoveTouchEvent(MotionEvent event) {
// Flush all touches down the drain till animation is over.
return true;
}
@Override
public boolean handleUpOrCancelTouchEvent(MotionEvent event) {
// Flush all touches down the drain till animation is over.
return true;
}
@Override
public void onAnimationEnd(Animator animation) {
issueStateTransition(mIdleState);
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mUpdateListener.onOverScrollUpdate(OverScrollBounceEffectDecoratorBase.this, STATE_BOUNCE_BACK, (Float) animation.getAnimatedValue());
}
@Override public void onAnimationStart(Animator animation) {}
@Override public void onAnimationCancel(Animator animation) {}
@Override public void onAnimationRepeat(Animator animation) {}
protected Animator createAnimator() {
final View view = mViewAdapter.getView();
mAnimAttributes.init(view);
// Set up a low-duration slow-down animation IN the drag direction.
// Exception: If wasn't dragging in 'forward' direction (or velocity=0 -- i.e. not dragging at all),
// skip slow-down anim directly to the bounce-back.
if (mVelocity == 0f || (mVelocity < 0 && mStartAttr.mDir) || (mVelocity > 0 && !mStartAttr.mDir)) {
return createBounceBackAnimator(mAnimAttributes.mAbsOffset);
}
// dt = (Vt - Vo) / a; Vt=0 ==> dt = -Vo / a
float slowdownDuration = -mVelocity / mDecelerateFactor;
slowdownDuration = (slowdownDuration < 0 ? 0 : slowdownDuration); // Happens in counter-direction dragging
// dx = (Vt^2 - Vo^2) / 2a; Vt=0 ==> dx = -Vo^2 / 2a
float slowdownDistance = -mVelocity * mVelocity / mDoubleDecelerateFactor;
float slowdownEndOffset = mAnimAttributes.mAbsOffset + slowdownDistance;
ObjectAnimator slowdownAnim = createSlowdownAnimator(view, (int) slowdownDuration, slowdownEndOffset);
// Set up the bounce back animation, bringing the view back into the original, pre-overscroll position (translation=0).
ObjectAnimator bounceBackAnim = createBounceBackAnimator(slowdownEndOffset);
// Play the 2 animations as a sequence.
AnimatorSet wholeAnim = new AnimatorSet();
wholeAnim.playSequentially(slowdownAnim, bounceBackAnim);
return wholeAnim;
}
protected ObjectAnimator createSlowdownAnimator(View view, int slowdownDuration, float slowdownEndOffset) {
ObjectAnimator slowdownAnim = ObjectAnimator.ofFloat(view, mAnimAttributes.mProperty, slowdownEndOffset);
slowdownAnim.setDuration(slowdownDuration);
slowdownAnim.setInterpolator(mBounceBackInterpolator);
slowdownAnim.addUpdateListener(this);
return slowdownAnim;
}
protected ObjectAnimator createBounceBackAnimator(float startOffset) {
final View view = mViewAdapter.getView();
// Duration is proportional to the view's size.
float bounceBackDuration = (Math.abs(startOffset) / mAnimAttributes.mMaxOffset) * MAX_BOUNCE_BACK_DURATION_MS;
ObjectAnimator bounceBackAnim = ObjectAnimator.ofFloat(view, mAnimAttributes.mProperty, mStartAttr.mAbsOffset);
bounceBackAnim.setDuration(Math.max((int) bounceBackDuration, MIN_BOUNCE_BACK_DURATION_MS));
bounceBackAnim.setInterpolator(mBounceBackInterpolator);
bounceBackAnim.addUpdateListener(this);
return bounceBackAnim;
}
}
public OverScrollBounceEffectDecoratorBase(IOverScrollDecoratorAdapter viewAdapter, float decelerateFactor, float touchDragRatioFwd, float touchDragRatioBck) {
mViewAdapter = viewAdapter;
mBounceBackState = new BounceBackState(decelerateFactor);
mOverScrollingState = new OverScrollingState(touchDragRatioFwd, touchDragRatioBck);
mIdleState = new IdleState();
mCurrentState = mIdleState;
attach();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
return mCurrentState.handleMoveTouchEvent(event);
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
return mCurrentState.handleUpOrCancelTouchEvent(event);
}
return false;
}
@Override
public void setOverScrollStateListener(IOverScrollStateListener listener) {
mStateListener = (listener != null ? listener : new OverScrollStateListenerStub());
}
@Override
public void setOverScrollUpdateListener(IOverScrollUpdateListener listener) {
mUpdateListener = (listener != null ? listener : new OverScrollUpdateListenerStub());
}
@Override
public int getCurrentState() {
return mCurrentState.getStateId();
}
@Override
public View getView() {
return mViewAdapter.getView();
}
protected void issueStateTransition(IDecoratorState state) {
IDecoratorState oldState = mCurrentState;
mCurrentState = state;
mCurrentState.handleEntryTransition(oldState);
}
protected void attach() {
getView().setOnTouchListener(this);
getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
}
@Override
public void detach() {
if (mCurrentState != mIdleState) {
Log.w(TAG, "Decorator detached while over-scroll is in effect. You might want to add a precondition of that getCurrentState()==STATE_IDLE, first.");
}
getView().setOnTouchListener(null);
getView().setOverScrollMode(View.OVER_SCROLL_ALWAYS);
}
protected abstract MotionAttributes createMotionAttributes();
protected abstract AnimationAttributes createAnimationAttributes();
protected abstract void translateView(View view, float offset);
protected abstract void translateViewAndEvent(View view, float offset, MotionEvent event);
}

View File

@ -1,78 +0,0 @@
package me.everything.android.ui.overscroll;
import android.view.View;
import android.widget.ScrollView;
import androidx.annotation.NonNull;
import androidx.core.widget.NestedScrollView;
import androidx.recyclerview.widget.RecyclerView;
import me.everything.android.ui.overscroll.adapters.NestedScrollViewOverScrollDecorAdapter;
import me.everything.android.ui.overscroll.adapters.RecyclerViewOverScrollDecorAdapter;
import me.everything.android.ui.overscroll.adapters.ScrollViewOverScrollDecorAdapter;
import me.everything.android.ui.overscroll.adapters.StaticOverScrollDecorAdapter;
/**
* @author amit
*/
public class OverScrollDecoratorHelper {
public static final int ORIENTATION_VERTICAL = 0;
public static final int ORIENTATION_HORIZONTAL = 1;
/**
* Set up the over-scroll effect over a specified {@link RecyclerView} view.
* <br/>Only recycler-views using <b>native</b> Android layout managers (i.e. {@link LinearLayoutManager},
* {@link GridLayoutManager} and {@link StaggeredGridLayoutManager}) are currently supported
* by this convenience method.
*
* @param recyclerView The view.
* @param orientation Either {@link #ORIENTATION_HORIZONTAL} or {@link #ORIENTATION_VERTICAL}.
* @return The over-scroll effect 'decorator', enabling further effect configuration.
*/
@NonNull
public static IOverScrollDecor setUpOverScroll(@NonNull RecyclerView recyclerView, int orientation) {
switch (orientation) {
case ORIENTATION_HORIZONTAL:
return new HorizontalOverScrollBounceEffectDecorator(
new RecyclerViewOverScrollDecorAdapter(recyclerView));
case ORIENTATION_VERTICAL:
return new VerticalOverScrollBounceEffectDecorator(
new RecyclerViewOverScrollDecorAdapter(recyclerView));
default:
throw new IllegalArgumentException("orientation");
}
}
@NonNull
public static IOverScrollDecor setUpOverScroll(@NonNull ScrollView scrollView) {
return new VerticalOverScrollBounceEffectDecorator(new ScrollViewOverScrollDecorAdapter(scrollView));
}
@NonNull
public static IOverScrollDecor setUpOverScroll(@NonNull NestedScrollView nestedScrollView) {
return new VerticalOverScrollBounceEffectDecorator(
new NestedScrollViewOverScrollDecorAdapter(nestedScrollView));
}
/**
* Set up the over-scroll over a generic view, assumed to always be over-scroll ready (e.g.
* a plain text field, image view).
*
* @param view The view.
* @param orientation One of {@link #ORIENTATION_HORIZONTAL} or {@link #ORIENTATION_VERTICAL}.
* @return The over-scroll effect 'decorator', enabling further effect configuration.
*/
public static IOverScrollDecor setUpStaticOverScroll(View view, int orientation) {
switch (orientation) {
case ORIENTATION_HORIZONTAL:
return new HorizontalOverScrollBounceEffectDecorator(new StaticOverScrollDecorAdapter(view));
case ORIENTATION_VERTICAL:
return new VerticalOverScrollBounceEffectDecorator(new StaticOverScrollDecorAdapter(view));
default:
throw new IllegalArgumentException("orientation");
}
}
}

View File

@ -1,100 +0,0 @@
package me.everything.android.ui.overscroll;
import android.view.MotionEvent;
import android.view.View;
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
/**
* A concrete implementation of {@link OverScrollBounceEffectDecoratorBase} for a vertical orientation.
*
* @author amit
*/
public class VerticalOverScrollBounceEffectDecorator extends OverScrollBounceEffectDecoratorBase {
protected static class MotionAttributesVertical extends MotionAttributes {
public boolean init(View view, MotionEvent event) {
// We must have history available to calc the dx. Normally it's there - if it isn't temporarily,
// we declare the event 'invalid' and expect it in consequent events.
if (event.getHistorySize() == 0) {
return false;
}
// Allow for counter-orientation-direction operations (e.g. item swiping) to run fluently.
final float dy = event.getY(0) - event.getHistoricalY(0, 0);
final float dx = event.getX(0) - event.getHistoricalX(0, 0);
if (Math.abs(dx) > Math.abs(dy)) {
return false;
}
mAbsOffset = view.getTranslationY();
mDeltaOffset = dy;
mDir = mDeltaOffset > 0;
return true;
}
}
protected static class AnimationAttributesVertical extends AnimationAttributes {
public AnimationAttributesVertical() {
mProperty = View.TRANSLATION_Y;
}
@Override
protected void init(View view) {
mAbsOffset = view.getTranslationY();
mMaxOffset = view.getHeight();
}
}
/**
* C'tor, creating the effect with default arguments:
* <br/>Touch-drag ratio in 'forward' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD.
* <br/>Touch-drag ratio in 'backwards' direction will be set to DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK.
* <br/>Deceleration factor (for the bounce-back effect) will be set to DEFAULT_DECELERATE_FACTOR.
*
* @param viewAdapter The view's encapsulation.
*/
public VerticalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter) {
this(viewAdapter, DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD, DEFAULT_TOUCH_DRAG_MOVE_RATIO_BCK,
DEFAULT_DECELERATE_FACTOR);
}
/**
* C'tor, creating the effect with explicit arguments.
*
* @param viewAdapter The view's encapsulation.
* @param touchDragRatioFwd Ratio of touch distance to actual drag distance when in 'forward' direction.
* @param touchDragRatioBck Ratio of touch distance to actual drag distance when in 'backward'
* direction (opposite to initial one).
* @param decelerateFactor Deceleration factor used when decelerating the motion to create the
* bounce-back effect.
*/
public VerticalOverScrollBounceEffectDecorator(IOverScrollDecoratorAdapter viewAdapter,
float touchDragRatioFwd, float touchDragRatioBck, float decelerateFactor) {
super(viewAdapter, decelerateFactor, touchDragRatioFwd, touchDragRatioBck);
}
@Override
protected AnimationAttributes createAnimationAttributes() {
return new AnimationAttributesVertical();
}
@Override
protected MotionAttributes createMotionAttributes() {
return new MotionAttributesVertical();
}
@Override
protected void translateView(View view, float offset) {
view.setTranslationY(offset);
}
@Override
protected void translateViewAndEvent(View view, float offset, MotionEvent event) {
view.setTranslationY(offset);
event.offsetLocation(offset - event.getY(0), 0f);
}
}

View File

@ -1,57 +0,0 @@
package me.everything.android.ui.overscroll.adapters;
import android.view.View;
import android.widget.AbsListView;
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
/**
* An adapter to enable over-scrolling over object of {@link AbsListView}, namely {@link
* android.widget.ListView} and it's extensions, and {@link android.widget.GridView}.
*
* @author amit
*
* @see HorizontalOverScrollBounceEffectDecorator
* @see VerticalOverScrollBounceEffectDecorator
*/
public class AbsListViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
protected final AbsListView mView;
public AbsListViewOverScrollDecorAdapter(AbsListView view) {
mView = view;
}
@Override
public View getView() {
return mView;
}
@Override
public boolean isInAbsoluteStart() {
return mView.getChildCount() > 0 && !canScrollListUp();
}
@Override
public boolean isInAbsoluteEnd() {
return mView.getChildCount() > 0 && !canScrollListDown();
}
public boolean canScrollListUp() {
// Ported from AbsListView#canScrollList() which isn't compatible to all API levels
final int firstTop = mView.getChildAt(0).getTop();
final int firstPosition = mView.getFirstVisiblePosition();
return firstPosition > 0 || firstTop < mView.getListPaddingTop();
}
public boolean canScrollListDown() {
// Ported from AbsListView#canScrollList() which isn't compatible to all API levels
final int childCount = mView.getChildCount();
final int itemsCount = mView.getCount();
final int firstPosition = mView.getFirstVisiblePosition();
final int lastPosition = firstPosition + childCount;
final int lastBottom = mView.getChildAt(childCount - 1).getBottom();
return lastPosition < itemsCount || lastBottom > mView.getHeight() - mView.getListPaddingBottom();
}
}

View File

@ -1,41 +0,0 @@
package me.everything.android.ui.overscroll.adapters;
import android.view.View;
import android.widget.HorizontalScrollView;
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
/**
* An adapter that enables over-scrolling support over a {@link HorizontalScrollView}.
* <br/>Seeing that {@link HorizontalScrollView} only supports horizontal scrolling, this adapter
* should only be used with a {@link HorizontalOverScrollBounceEffectDecorator}.
*
* @author amit
*
* @see HorizontalOverScrollBounceEffectDecorator
* @see VerticalOverScrollBounceEffectDecorator
*/
public class HorizontalScrollViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
protected final HorizontalScrollView mView;
public HorizontalScrollViewOverScrollDecorAdapter(HorizontalScrollView view) {
mView = view;
}
@Override
public View getView() {
return mView;
}
@Override
public boolean isInAbsoluteStart() {
return !mView.canScrollHorizontally(-1);
}
@Override
public boolean isInAbsoluteEnd() {
return !mView.canScrollHorizontally(1);
}
}

View File

@ -1,33 +0,0 @@
package me.everything.android.ui.overscroll.adapters;
import android.view.View;
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
/**
* @author amitd
*
* @see HorizontalOverScrollBounceEffectDecorator
*/
public interface IOverScrollDecoratorAdapter {
View getView();
/**
* Is view in it's absolute start position - such that a negative over-scroll can potentially
* be initiated. For example, in list-views, this is synonymous with the first item being
* fully visible.
*
* @return Whether in absolute start position.
*/
boolean isInAbsoluteStart();
/**
* Is view in it's absolute end position - such that an over-scroll can potentially
* be initiated. For example, in list-views, this is synonymous with the last item being
* fully visible.
*
* @return Whether in absolute end position.
*/
boolean isInAbsoluteEnd();
}

View File

@ -1,40 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package me.everything.android.ui.overscroll.adapters;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.widget.NestedScrollView;
public class NestedScrollViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
protected final NestedScrollView mView;
public NestedScrollViewOverScrollDecorAdapter(@NonNull NestedScrollView view) {
this.mView = view;
}
public View getView() {
return this.mView;
}
public boolean isInAbsoluteEnd() {
return !this.mView.canScrollVertically(1);
}
public boolean isInAbsoluteStart() {
return !this.mView.canScrollVertically(-1);
}
}

View File

@ -1,237 +0,0 @@
package me.everything.android.ui.overscroll.adapters;
import android.graphics.Canvas;
import android.view.View;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import java.util.List;
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
/**
* @author amitd
* @see HorizontalOverScrollBounceEffectDecorator
* @see VerticalOverScrollBounceEffectDecorator
*/
public class RecyclerViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
private static class ItemTouchHelperCallbackWrapper extends ItemTouchHelper.Callback {
final ItemTouchHelper.Callback mCallback;
private ItemTouchHelperCallbackWrapper(ItemTouchHelper.Callback callback) {
mCallback = callback;
}
@Override
public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current,
RecyclerView.ViewHolder target) {
return mCallback.canDropOver(recyclerView, current, target);
}
@Override
public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected,
List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
return mCallback.chooseDropTarget(selected, dropTargets, curX, curY);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
mCallback.clearView(recyclerView, viewHolder);
}
@Override
public int convertToAbsoluteDirection(int flags, int layoutDirection) {
return mCallback.convertToAbsoluteDirection(flags, layoutDirection);
}
@Override
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx,
float animateDy) {
return mCallback.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);
}
@Override
public int getBoundingBoxMargin() {
return mCallback.getBoundingBoxMargin();
}
@Override
public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
return mCallback.getMoveThreshold(viewHolder);
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return mCallback.getMovementFlags(recyclerView, viewHolder);
}
@Override
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
return mCallback.getSwipeThreshold(viewHolder);
}
@Override
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds,
int totalSize, long msSinceStartScroll) {
return mCallback.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize,
msSinceStartScroll);
}
@Override
public boolean isItemViewSwipeEnabled() {
return mCallback.isItemViewSwipeEnabled();
}
@Override
public boolean isLongPressDragEnabled() {
return mCallback.isLongPressDragEnabled();
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX,
float dY, int actionState, boolean isCurrentlyActive) {
mCallback.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX,
float dY, int actionState, boolean isCurrentlyActive) {
mCallback.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
return mCallback.onMove(recyclerView, viewHolder, target);
}
@Override
public void onMoved(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int fromPos,
RecyclerView.ViewHolder target, int toPos, int x, int y) {
mCallback.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
mCallback.onSelectedChanged(viewHolder, actionState);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mCallback.onSwiped(viewHolder, direction);
}
}
protected class ImplHorizLayout implements Impl {
@Override
public boolean isInAbsoluteEnd() {
return !mRecyclerView.canScrollHorizontally(1);
}
@Override
public boolean isInAbsoluteStart() {
return !mRecyclerView.canScrollHorizontally(-1);
}
}
protected class ImplVerticalLayout implements Impl {
@Override
public boolean isInAbsoluteEnd() {
return !mRecyclerView.canScrollVertically(1);
}
@Override
public boolean isInAbsoluteStart() {
return !mRecyclerView.canScrollVertically(-1);
}
}
/**
* A delegation of the adapter implementation of this view that should provide the processing
* of {@link #isInAbsoluteStart()} and {@link #isInAbsoluteEnd()}. Essentially needed simply
* because the implementation depends on the layout manager implementation being used.
*/
protected interface Impl {
boolean isInAbsoluteEnd();
boolean isInAbsoluteStart();
}
protected final Impl mImpl;
protected boolean mIsItemTouchInEffect = false;
protected final RecyclerView mRecyclerView;
public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager ||
layoutManager instanceof StaggeredGridLayoutManager) {
final int orientation =
(layoutManager instanceof LinearLayoutManager
? ((LinearLayoutManager) layoutManager).getOrientation()
: ((StaggeredGridLayoutManager) layoutManager).getOrientation());
if (orientation == LinearLayoutManager.HORIZONTAL) {
mImpl = new ImplHorizLayout();
} else {
mImpl = new ImplVerticalLayout();
}
} else {
throw new IllegalArgumentException(
"Recycler views with custom layout managers are not supported by this adapter out of the box." +
"Try implementing and providing an explicit 'impl' parameter to the other c'tors, or otherwise create a custom adapter subclass of your own.");
}
}
public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView, Impl impl) {
mRecyclerView = recyclerView;
mImpl = impl;
}
public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView,
ItemTouchHelper.Callback itemTouchHelperCallback) {
this(recyclerView);
setUpTouchHelperCallback(itemTouchHelperCallback);
}
public RecyclerViewOverScrollDecorAdapter(RecyclerView recyclerView, Impl impl,
ItemTouchHelper.Callback itemTouchHelperCallback) {
this(recyclerView, impl);
setUpTouchHelperCallback(itemTouchHelperCallback);
}
@Override
public View getView() {
return mRecyclerView;
}
@Override
public boolean isInAbsoluteEnd() {
return !mIsItemTouchInEffect && mImpl.isInAbsoluteEnd();
}
@Override
public boolean isInAbsoluteStart() {
return !mIsItemTouchInEffect && mImpl.isInAbsoluteStart();
}
protected void setUpTouchHelperCallback(final ItemTouchHelper.Callback itemTouchHelperCallback) {
new ItemTouchHelper(new ItemTouchHelperCallbackWrapper(itemTouchHelperCallback) {
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
mIsItemTouchInEffect = actionState != 0;
super.onSelectedChanged(viewHolder, actionState);
}
}).attachToRecyclerView(mRecyclerView);
}
}

View File

@ -1,43 +0,0 @@
package me.everything.android.ui.overscroll.adapters;
import android.view.View;
import android.widget.ScrollView;
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
/**
* An adapter that enables over-scrolling over a {@link ScrollView}.
* <br/>Seeing that {@link ScrollView} only supports vertical scrolling, this adapter
* should only be used with a {@link VerticalOverScrollBounceEffectDecorator}. For horizontal
* over-scrolling, use {@link HorizontalScrollViewOverScrollDecorAdapter} in conjunction with
* a {@link android.widget.HorizontalScrollView}.
*
* @author amit
*
* @see HorizontalOverScrollBounceEffectDecorator
* @see VerticalOverScrollBounceEffectDecorator
*/
public class ScrollViewOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
protected final ScrollView mView;
public ScrollViewOverScrollDecorAdapter(ScrollView view) {
mView = view;
}
@Override
public View getView() {
return mView;
}
@Override
public boolean isInAbsoluteStart() {
return !mView.canScrollVertically(-1);
}
@Override
public boolean isInAbsoluteEnd() {
return !mView.canScrollVertically(1);
}
}

View File

@ -1,38 +0,0 @@
package me.everything.android.ui.overscroll.adapters;
import android.view.View;
import me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator;
import me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator;
/**
* A static adapter for views that are ALWAYS over-scroll-able (e.g. image view).
*
* @author amit
*
* @see HorizontalOverScrollBounceEffectDecorator
* @see VerticalOverScrollBounceEffectDecorator
*/
public class StaticOverScrollDecorAdapter implements IOverScrollDecoratorAdapter {
protected final View mView;
public StaticOverScrollDecorAdapter(View view) {
mView = view;
}
@Override
public View getView() {
return mView;
}
@Override
public boolean isInAbsoluteStart() {
return true;
}
@Override
public boolean isInAbsoluteEnd() {
return true;
}
}

View File

@ -1,640 +0,0 @@
package me.everything.android.ui.overscroll;
import android.view.MotionEvent;
import android.view.View;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
import static me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator.DEFAULT_DECELERATE_FACTOR;
import static me.everything.android.ui.overscroll.HorizontalOverScrollBounceEffectDecorator.DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
import static me.everything.android.ui.overscroll.IOverScrollState.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
* @author amitd
*/
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class HorizontalOverScrollBounceEffectDecoratorTest {
View mView;
IOverScrollDecoratorAdapter mViewAdapter;
IOverScrollStateListener mStateListener;
IOverScrollUpdateListener mUpdateListener;
@Before
public void setUp() throws Exception {
mView = mock(View.class);
mViewAdapter = mock(IOverScrollDecoratorAdapter.class);
when(mViewAdapter.getView()).thenReturn(mView);
mStateListener = mock(IOverScrollStateListener.class);
mUpdateListener = mock(IOverScrollUpdateListener.class);
}
@Test
public void detach_decoratorIsAttached_detachFromView() throws Exception {
// Arrange
HorizontalOverScrollBounceEffectDecorator uut = new HorizontalOverScrollBounceEffectDecorator(mViewAdapter);
// Act
uut.detach();
// Assert
verify(mView).setOnTouchListener(eq((View.OnTouchListener) null));
verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS);
}
@Test
public void detach_overScrollInEffect_detachFromView() throws Exception {
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
uut.onTouch(mView, createShortRightMoveEvent());
// Act
uut.detach();
// Assert
verify(mView).setOnTouchListener(eq((View.OnTouchListener) null));
verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS);
}
/*
* Move-action event
*/
@Test
public void onTouchMoveAction_notInViewEnds_ignoreTouchEvent() throws Exception {
// Arrange
MotionEvent event = createShortRightMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
// Act
boolean ret = uut.onTouch(mView, event);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView, never()).setTranslationY(anyFloat());
assertFalse(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_dragRightInLeftEnd_overscrollRight() throws Exception {
// Arrange
MotionEvent event = createShortRightMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
// Act
final boolean ret = uut.onTouch(mView, event);
// Assert
final float expectedTransX = (event.getX() - event.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
verify(mView).setTranslationX(expectedTransX);
verify(mView, never()).setTranslationY(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransX));
}
@Test
public void onTouchMoveAction_dragLeftInRightEnd_overscrollLeft() throws Exception {
// Arrange
MotionEvent event = createShortLeftMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
// Act
final boolean ret = uut.onTouch(mView, event);
// Assert
final float expectedTransX = (event.getX() - event.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
verify(mView).setTranslationX(expectedTransX);
verify(mView, never()).setTranslationY(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransX));
}
@Test
public void onTouchMoveAction_dragLeftInLeftEnd_ignoreTouchEvent() throws Exception {
// Arrange
MotionEvent event = createShortLeftMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
// Act
final boolean ret = uut.onTouch(mView, event);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView, never()).setTranslationY(anyFloat());
assertFalse(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_dragRightInRightEnd_ignoreTouchEvent() throws Exception {
// Arrange
MotionEvent event = createShortRightMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
// Act
boolean ret = uut.onTouch(mView, event);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView, never()).setTranslationY(anyFloat());
assertFalse(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_2ndRightDragInLeftEnd_overscrollRightFurther() throws Exception {
// Arrange
// Bring UUT to a right-overscroll state
MotionEvent event1 = createShortRightMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
uut.onTouch(mView, event1);
reset(mView);
// Create 2nd right-drag event
MotionEvent event2 = createLongRightMoveEvent();
// Act
final boolean ret = uut.onTouch(mView, event2);
// Assert
final float expectedTransX1 = (event1.getX() - event1.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
final float expectedTransX2 = (event2.getX() - event2.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
verify(mView).setTranslationX(expectedTransX2);
verify(mView, never()).setTranslationY(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
// State-change listener called only once?
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransX1));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransX2));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_2ndLeftDragInRightEnd_overscrollLeftFurther() throws Exception {
// Arrange
// Bring UUT to a left-overscroll state
MotionEvent event1 = createShortLeftMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
uut.onTouch(mView, event1);
reset(mView);
// Create 2nd left-drag event
MotionEvent event2 = createLongLeftMoveEvent();
// Act
final boolean ret = uut.onTouch(mView, event2);
// Assert
final float expectedTransX1 = (event1.getX() - event1.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
final float expectedTransX2 = (event2.getX() - event2.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
verify(mView).setTranslationX(expectedTransX2);
verify(mView, never()).setTranslationY(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
// State-change listener called only once?
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransX1));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransX2));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
/**
* When over-scroll has already started (to the right in this case) and suddenly the user changes
* their mind and scrolls a bit in the other direction:
* <br/>We expect the <b>touch to still be intercepted</b> in that case, and the <b>overscroll to
* remain in effect</b>.
*/
@Test
public void onTouchMoveAction_dragLeftWhenRightOverscolled_continueOverscrollingLeft() throws Exception {
// Arrange
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
final float touchDragRatioFwd = 3f;
final float touchDragRatioBck = 3f;
// Bring UUT to a right-overscroll state
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
HorizontalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
MotionEvent eventMoveRight = createLongRightMoveEvent();
uut.onTouch(mView, eventMoveRight);
reset(mView);
float startTransX = (eventMoveRight.getX() - eventMoveRight.getHistoricalX(0)) / touchDragRatioFwd;
when(mView.getTranslationX()).thenReturn(startTransX);
// Create the left-drag event
MotionEvent eventMoveLeft = createShortLeftMoveEvent();
// Act
boolean ret = uut.onTouch(mView, eventMoveLeft);
// Assert
float expectedTransX = startTransX +
(eventMoveLeft.getX() - eventMoveLeft.getHistoricalX(0)) / touchDragRatioBck;
verify(mView).setTranslationX(expectedTransX);
verify(mView, never()).setTranslationY(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
// State-change listener called only once?
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(startTransX));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransX));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
/**
* When over-scroll has already started (to the left in this case) and suddenly the user changes
* their mind and scrolls a bit in the other direction:
* <br/>We expect the <b>touch to still be intercepted</b> in that case, and the <b>overscroll to remain in effect</b>.
*/
@Test
public void onTouchMoveAction_dragRightWhenLeftOverscolled_continueOverscrollingRight() throws Exception {
// Arrange
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
final float touchDragRatioFwd = 3f;
final float touchDragRatioBck = 3f;
// Bring UUT to a left-overscroll state
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
HorizontalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
MotionEvent eventMoveLeft = createLongLeftMoveEvent();
uut.onTouch(mView, eventMoveLeft);
reset(mView);
float startTransX = (eventMoveLeft.getX() - eventMoveLeft.getHistoricalX(0)) / touchDragRatioFwd;
when(mView.getTranslationX()).thenReturn(startTransX);
// Create the right-drag event
MotionEvent eventMoveRight = createShortRightMoveEvent();
// Act
boolean ret = uut.onTouch(mView, eventMoveRight);
// Assert
float expectedTransX = startTransX + (eventMoveRight.getX() - eventMoveRight.getHistoricalX(0)) / touchDragRatioBck;
verify(mView).setTranslationX(expectedTransX);
verify(mView, never()).setTranslationY(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
// State-change listener called only once?
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(startTransX));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransX));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_undragWhenRightOverscrolled_endOverscrolling() throws Exception {
// Arrange
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
final float touchDragRatioFwd = 3f;
final float touchDragRatioBck = 3f;
// Bring UUT to a right-overscroll state
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
HorizontalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
MotionEvent eventMoveRight = createLongRightMoveEvent();
uut.onTouch(mView, eventMoveRight);
reset(mView);
float startTransX = (eventMoveRight.getX() - eventMoveRight.getHistoricalX(0)) / touchDragRatioFwd;
when(mView.getTranslationX()).thenReturn(startTransX);
// Create the left-drag event
MotionEvent eventMoveLeft = createLongLeftMoveEvent();
// Act
boolean ret = uut.onTouch(mView, eventMoveLeft);
// Assert
verify(mView).setTranslationX(0);
verify(mView, never()).setTranslationY(anyFloat());
assertTrue(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
// State-change listener invoked to say drag-on and drag-off (idle).
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_DRAG_START_SIDE), eq(STATE_IDLE));
verify(mStateListener, times(2)).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(startTransX));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(0f));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_undragWhenLeftOverscrolled_endOverscrolling() throws Exception {
// Arrange
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
final float touchDragRatioFwd = 3f;
final float touchDragRatioBck = 3f;
// Bring UUT to a left-overscroll state
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
HorizontalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
MotionEvent eventMoveLeft = createLongLeftMoveEvent();
uut.onTouch(mView, eventMoveLeft);
reset(mView);
float startTransX = (eventMoveLeft.getX() - eventMoveLeft.getHistoricalX(0)) / touchDragRatioFwd;
when(mView.getTranslationX()).thenReturn(startTransX);
// Create the left-drag event
MotionEvent eventMoveRight = createLongRightMoveEvent();
// Act
boolean ret = uut.onTouch(mView, eventMoveRight);
// Assert
verify(mView).setTranslationX(0);
verify(mView, never()).setTranslationY(anyFloat());
assertTrue(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
// State-change listener invoked to say drag-on and drag-off (idle).
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_DRAG_END_SIDE), eq(STATE_IDLE));
verify(mStateListener, times(2)).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(startTransX));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(0f));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
/*
* Up action event
*/
@Test
public void onTouchUpAction_eventWhenNotOverscrolled_ignoreTouchEvent() throws Exception {
// Arrange
MotionEvent event = createDefaultUpActionEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
// Act
boolean ret = uut.onTouch(mView, event);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView, never()).setTranslationY(anyFloat());
assertFalse(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
/**
* TODO: Make this work using a decent animation shadows / newer Robolectric
* @throws Exception
*/
@Ignore
@Test
public void onTouchUpAction_eventWhenLeftOverscrolling_smoothScrollBackToRightEnd() throws Exception {
// Arrange
// Bring UUT to a left-overscroll state
MotionEvent moveEvent = createShortLeftMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
HorizontalOverScrollBounceEffectDecorator uut = getUUT();
uut.onTouch(mView, moveEvent);
reset(mView);
// Make the view as though it's been moved by the move event
float viewX = moveEvent.getX();
when(mView.getTranslationX()).thenReturn(viewX);
MotionEvent upEvent = createDefaultUpActionEvent();
// Act
boolean ret = uut.onTouch(mView, upEvent);
// Assert
assertTrue(ret);
verify(mView, atLeastOnce()).setTranslationX(anyFloat());
}
protected MotionEvent createShortRightMoveEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
when(event.getX()).thenReturn(100f);
when(event.getY()).thenReturn(200f);
when(event.getX(0)).thenReturn(100f);
when(event.getY(0)).thenReturn(200f);
when(event.getHistorySize()).thenReturn(1);
when(event.getHistoricalX(eq(0))).thenReturn(80f);
when(event.getHistoricalY(eq(0))).thenReturn(190f);
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(80f);
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(190f);
return event;
}
protected MotionEvent createLongRightMoveEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
when(event.getX()).thenReturn(150f);
when(event.getY()).thenReturn(250f);
when(event.getX(0)).thenReturn(150f);
when(event.getY(0)).thenReturn(250f);
when(event.getHistorySize()).thenReturn(1);
when(event.getHistoricalX(eq(0))).thenReturn(100f);
when(event.getHistoricalY(eq(0))).thenReturn(200f);
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(100f);
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(200f);
return event;
}
protected MotionEvent createShortLeftMoveEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
when(event.getX()).thenReturn(100f);
when(event.getY()).thenReturn(200f);
when(event.getX(0)).thenReturn(100f);
when(event.getY(0)).thenReturn(200f);
when(event.getHistorySize()).thenReturn(1);
when(event.getHistoricalX(eq(0))).thenReturn(120f);
when(event.getHistoricalY(eq(0))).thenReturn(220f);
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(120f);
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(220f);
return event;
}
protected MotionEvent createLongLeftMoveEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
when(event.getX()).thenReturn(50f);
when(event.getY()).thenReturn(150f);
when(event.getX(0)).thenReturn(50f);
when(event.getY(0)).thenReturn(150f);
when(event.getHistorySize()).thenReturn(1);
when(event.getHistoricalX(eq(0))).thenReturn(100f);
when(event.getHistoricalY(eq(0))).thenReturn(200f);
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(100f);
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(200f);
return event;
}
protected MotionEvent createDefaultUpActionEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_UP);
return event;
}
protected HorizontalOverScrollBounceEffectDecorator getUUT() {
HorizontalOverScrollBounceEffectDecorator uut = new HorizontalOverScrollBounceEffectDecorator(mViewAdapter);
uut.setOverScrollStateListener(mStateListener);
uut.setOverScrollUpdateListener(mUpdateListener);
return uut;
}
protected HorizontalOverScrollBounceEffectDecorator getUUT(float touchDragRatioFwd, float touchDragRatioBck) {
HorizontalOverScrollBounceEffectDecorator uut = new HorizontalOverScrollBounceEffectDecorator(mViewAdapter, touchDragRatioFwd, touchDragRatioBck, DEFAULT_DECELERATE_FACTOR);
uut.setOverScrollStateListener(mStateListener);
uut.setOverScrollUpdateListener(mUpdateListener);
return uut;
}
}

View File

@ -1,600 +0,0 @@
package me.everything.android.ui.overscroll;
import android.view.MotionEvent;
import android.view.View;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import me.everything.android.ui.overscroll.adapters.IOverScrollDecoratorAdapter;
import static me.everything.android.ui.overscroll.IOverScrollState.*;
import static me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator.DEFAULT_DECELERATE_FACTOR;
import static me.everything.android.ui.overscroll.VerticalOverScrollBounceEffectDecorator.DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
* @author amitd
*/
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class VerticalOverScrollBounceEffectDecoratorTest {
View mView;
IOverScrollDecoratorAdapter mViewAdapter;
IOverScrollStateListener mStateListener;
IOverScrollUpdateListener mUpdateListener;
@Before
public void setUp() throws Exception {
mView = mock(View.class);
mViewAdapter = mock(IOverScrollDecoratorAdapter.class);
when(mViewAdapter.getView()).thenReturn(mView);
mStateListener = mock(IOverScrollStateListener.class);
mUpdateListener = mock(IOverScrollUpdateListener.class);
}
@Test
public void detach_decoratorIsAttached_detachFromView() throws Exception {
// Arrange
HorizontalOverScrollBounceEffectDecorator uut = new HorizontalOverScrollBounceEffectDecorator(mViewAdapter);
// Act
uut.detach();
// Assert
verify(mView).setOnTouchListener(eq((View.OnTouchListener) null));
verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS);
}
@Test
public void detach_overScrollInEffect_detachFromView() throws Exception {
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
VerticalOverScrollBounceEffectDecorator uut = getUUT();
uut.onTouch(mView, createShortDownwardsMoveEvent());
// Act
uut.detach();
// Assert
verify(mView).setOnTouchListener(eq((View.OnTouchListener) null));
verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS);
}
/*
* Move-action event
*/
@Test
public void onTouchMoveAction_notInViewEnds_ignoreTouchEvent() throws Exception {
// Arrange
MotionEvent event = createShortDownwardsMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
VerticalOverScrollBounceEffectDecorator uut = getUUT();
// Act
boolean ret = uut.onTouch(mView, event);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView, never()).setTranslationY(anyFloat());
assertFalse(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
verify(mStateListener, never()).onOverScrollStateChange(eq(uut),anyInt(), anyInt());
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_dragDownInUpperEnd_overscrollDownwards() throws Exception {
// Arrange
MotionEvent event = createShortDownwardsMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
VerticalOverScrollBounceEffectDecorator uut = getUUT();
// Act
boolean ret = uut.onTouch(mView, event);
// Assert
float expectedTransY = (event.getY() - event.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
verify(mView).setTranslationY(expectedTransY);
verify(mView, never()).setTranslationX(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransY));
}
@Test
public void onTouchMoveAction_dragUpInBottomEnd_overscrollUpwards() throws Exception {
// Arrange
MotionEvent event = createShortUpwardsMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
VerticalOverScrollBounceEffectDecorator uut = getUUT();
// Act
boolean ret = uut.onTouch(mView, event);
// Assert
float expectedTransY = (event.getY() - event.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
verify(mView).setTranslationY(expectedTransY);
verify(mView, never()).setTranslationX(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransY));
}
@Test
public void onTouchMoveAction_dragUpInUpperEnd_ignoreTouchEvent() throws Exception {
// Arrange
MotionEvent event = createShortUpwardsMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
VerticalOverScrollBounceEffectDecorator uut = getUUT();
// Act
boolean ret = uut.onTouch(mView, event);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView, never()).setTranslationY(anyFloat());
assertFalse(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_dragDownInBottomEnd_ignoreTouchEvent() throws Exception {
// Arrange
MotionEvent event = createShortDownwardsMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
VerticalOverScrollBounceEffectDecorator uut = getUUT();
// Act
boolean ret = uut.onTouch(mView, event);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView, never()).setTranslationY(anyFloat());
assertFalse(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_2ndDownDragInUpperEnd_overscrollDownwardsFurther() throws Exception {
// Arrange
// Bring UUT to a downwards-overscroll state
MotionEvent event1 = createShortDownwardsMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
VerticalOverScrollBounceEffectDecorator uut = getUUT();
uut.onTouch(mView, event1);
reset(mView);
// Create 2nd downwards-drag event
MotionEvent event2 = createLongDownwardsMoveEvent();
// Act
final boolean ret = uut.onTouch(mView, event2);
// Assert
final float expectedTransY1 = (event1.getY() - event1.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
final float expectedTransY2 = (event2.getY() - event2.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
verify(mView).setTranslationY(expectedTransY2);
verify(mView, never()).setTranslationX(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
// State-change listener called only once?
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransY1));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransY2));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_2ndUpDragInBottomEnd_overscrollUpwardsFurther() throws Exception {
// Arrange
// Bring UUT to an upwards-overscroll state
MotionEvent event1 = createShortUpwardsMoveEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
VerticalOverScrollBounceEffectDecorator uut = getUUT();
uut.onTouch(mView, event1);
reset(mView);
// Create 2nd upward-drag event
MotionEvent event2 = createLongUpwardsMoveEvent();
// Act
final boolean ret = uut.onTouch(mView, event2);
// Assert
final float expectedTransY1 = (event1.getY() - event1.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
final float expectedTransY2 = (event2.getY() - event2.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD;
verify(mView).setTranslationY(expectedTransY2);
verify(mView, never()).setTranslationX(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
// State-change listener called only once?
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransY1));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransY2));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
/**
* When over-scroll has already started (downwards in this case) and suddenly the user changes
* their mind and scrolls a bit in the other direction:
* <br/>We expect the <b>touch to still be intercepted</b> in that case, and the <b>overscroll to remain in effect</b>.
*/
@Test
public void onTouchMoveAction_dragUpWhenDownOverscolled_continueOverscrollingUpwards() throws Exception {
// Arrange
// In down & up drag tests we use equal ratios to avoid the effect's under-scroll handling
final float touchDragRatioFwd = 3f;
final float touchDragRatioBck = 3f;
// Bring UUT to a downwrads-overscroll state
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
VerticalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
MotionEvent eventMoveRight = createLongDownwardsMoveEvent();
uut.onTouch(mView, eventMoveRight);
reset(mView);
float startTransY = (eventMoveRight.getY() - eventMoveRight.getHistoricalY(0)) / touchDragRatioFwd;
when(mView.getTranslationY()).thenReturn(startTransY);
// Create the up-drag event
MotionEvent eventMoveUpwards = createShortUpwardsMoveEvent();
// Act
boolean ret = uut.onTouch(mView, eventMoveUpwards);
// Assert
float expectedTransY = startTransY +
(eventMoveUpwards.getY() - eventMoveUpwards.getHistoricalY(0)) / touchDragRatioBck;
verify(mView).setTranslationY(expectedTransY);
verify(mView, never()).setTranslationX(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState());
// State-change listener called only once?
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(startTransY));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransY));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
/**
* When over-scroll has already started (upwards in this case) and suddenly the user changes
* their mind and scrolls a bit in the other direction:
* <br/>We expect the <b>touch to still be intercepted</b> in that case, and the <b>overscroll to remain in effect</b>.
*/
@Test
public void onTouchMoveAction_dragDownWhenUpOverscolled_continueOverscrollingDownwards() throws Exception {
// Arrange
// In up & down drag tests we use equal ratios to avoid the effect's under-scroll handling
final float touchDragRatioFwd = 3f;
final float touchDragRatioBck = 3f;
// Bring UUT to an upwards-overscroll state
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
VerticalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
MotionEvent eventMoveUp = createLongUpwardsMoveEvent();
uut.onTouch(mView, eventMoveUp);
reset(mView);
float startTransY = (eventMoveUp.getY() - eventMoveUp.getHistoricalY(0)) / touchDragRatioFwd;
when(mView.getTranslationY()).thenReturn(startTransY);
// Create the down-drag event
MotionEvent eventMoveDown = createShortDownwardsMoveEvent();
// Act
boolean ret = uut.onTouch(mView, eventMoveDown);
// Assert
float expectedTransY = startTransY + (eventMoveDown.getY() - eventMoveDown.getHistoricalY(0)) / touchDragRatioBck;
verify(mView).setTranslationY(expectedTransY);
verify(mView, never()).setTranslationX(anyFloat());
assertTrue(ret);
assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState());
// State-change listener called only once?
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(startTransY));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransY));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_undragWhenDownOverscrolled_endOverscrolling() throws Exception {
// Arrange
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
final float touchDragRatioFwd = 3f;
final float touchDragRatioBck = 3f;
// Bring UUT to a downwards-overscroll state
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false);
VerticalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
MotionEvent eventMoveDown = createLongDownwardsMoveEvent();
uut.onTouch(mView, eventMoveDown);
reset(mView);
float startTransX = (eventMoveDown.getX() - eventMoveDown.getHistoricalX(0)) / touchDragRatioFwd;
when(mView.getTranslationX()).thenReturn(startTransX);
// Create the (negative) upwards-drag event
MotionEvent eventMoveUp = createLongUpwardsMoveEvent();
// Act
boolean ret = uut.onTouch(mView, eventMoveUp);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView).setTranslationY(0);
assertTrue(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
// State-change listener invoked to say drag-on and drag-off (idle).
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_DRAG_START_SIDE), eq(STATE_IDLE));
verify(mStateListener, times(2)).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(startTransX));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(0f));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
@Test
public void onTouchMoveAction_undragWhenUpOverscrolled_endOverscrolling() throws Exception {
// Arrange
// In left & right tests we use equal ratios to avoid the effect's under-scroll handling
final float touchDragRatioFwd = 3f;
final float touchDragRatioBck = 3f;
// Bring UUT to a left-overscroll state
when(mViewAdapter.isInAbsoluteStart()).thenReturn(false);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
VerticalOverScrollBounceEffectDecorator uut = getUUT(touchDragRatioFwd, touchDragRatioBck);
MotionEvent eventMoveUp = createLongUpwardsMoveEvent();
uut.onTouch(mView, eventMoveUp);
reset(mView);
float startTransX = (eventMoveUp.getX() - eventMoveUp.getHistoricalX(0)) / touchDragRatioFwd;
when(mView.getTranslationX()).thenReturn(startTransX);
// Create the (negative) downwards-drag event
MotionEvent eventMoveDown = createLongDownwardsMoveEvent();
// Act
boolean ret = uut.onTouch(mView, eventMoveDown);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView).setTranslationY(0);
assertTrue(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
// State-change listener invoked to say drag-on and drag-off (idle).
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE));
verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_DRAG_END_SIDE), eq(STATE_IDLE));
verify(mStateListener, times(2)).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
// Update-listener called exactly twice?
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(startTransX));
verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(0f));
verify(mUpdateListener, times(2)).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
/*
* Up action event
*/
@Test
public void onTouchUpAction_eventWhenNotOverscrolled_ignoreTouchEvent() throws Exception {
// Arrange
MotionEvent event = createDefaultUpActionEvent();
when(mViewAdapter.isInAbsoluteStart()).thenReturn(true);
when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true);
VerticalOverScrollBounceEffectDecorator uut = getUUT();
// Act
boolean ret = uut.onTouch(mView, event);
// Assert
verify(mView, never()).setTranslationX(anyFloat());
verify(mView, never()).setTranslationY(anyFloat());
assertFalse(ret);
assertEquals(STATE_IDLE, uut.getCurrentState());
verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt());
verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat());
}
protected MotionEvent createShortDownwardsMoveEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
when(event.getX()).thenReturn(200f);
when(event.getY()).thenReturn(100f);
when(event.getX(0)).thenReturn(200f);
when(event.getY(0)).thenReturn(100f);
when(event.getHistorySize()).thenReturn(1);
when(event.getHistoricalX(eq(0))).thenReturn(190f);
when(event.getHistoricalY(eq(0))).thenReturn(80f);
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(190f);
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(80f);
return event;
}
protected MotionEvent createLongDownwardsMoveEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
when(event.getX()).thenReturn(250f);
when(event.getY()).thenReturn(150f);
when(event.getX(0)).thenReturn(250f);
when(event.getY(0)).thenReturn(150f);
when(event.getHistorySize()).thenReturn(1);
when(event.getHistoricalX(eq(0))).thenReturn(200f);
when(event.getHistoricalY(eq(0))).thenReturn(100f);
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(200f);
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(100f);
return event;
}
protected MotionEvent createShortUpwardsMoveEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
when(event.getX()).thenReturn(200f);
when(event.getY()).thenReturn(100f);
when(event.getX(0)).thenReturn(200f);
when(event.getY(0)).thenReturn(100f);
when(event.getHistorySize()).thenReturn(1);
when(event.getHistoricalX(eq(0))).thenReturn(220f);
when(event.getHistoricalY(eq(0))).thenReturn(120f);
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(220f);
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(120f);
return event;
}
protected MotionEvent createLongUpwardsMoveEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_MOVE);
when(event.getX()).thenReturn(200f);
when(event.getY()).thenReturn(100f);
when(event.getX(0)).thenReturn(200f);
when(event.getY(0)).thenReturn(100f);
when(event.getHistorySize()).thenReturn(1);
when(event.getHistoricalX(eq(0))).thenReturn(250f);
when(event.getHistoricalY(eq(0))).thenReturn(150f);
when(event.getHistoricalX(eq(0), eq(0))).thenReturn(250f);
when(event.getHistoricalY(eq(0), eq(0))).thenReturn(150f);
return event;
}
protected MotionEvent createDefaultUpActionEvent() {
MotionEvent event = mock(MotionEvent.class);
when(event.getAction()).thenReturn(MotionEvent.ACTION_UP);
return event;
}
protected VerticalOverScrollBounceEffectDecorator getUUT() {
VerticalOverScrollBounceEffectDecorator uut = new VerticalOverScrollBounceEffectDecorator(mViewAdapter);
uut.setOverScrollStateListener(mStateListener);
uut.setOverScrollUpdateListener(mUpdateListener);
return uut;
}
protected VerticalOverScrollBounceEffectDecorator getUUT(float touchDragRatioFwd, float touchDragRatioBck) {
VerticalOverScrollBounceEffectDecorator uut = new VerticalOverScrollBounceEffectDecorator(mViewAdapter, touchDragRatioFwd, touchDragRatioBck, DEFAULT_DECELERATE_FACTOR);
uut.setOverScrollStateListener(mStateListener);
uut.setOverScrollUpdateListener(mUpdateListener);
return uut;
}
}