Update UI code

Make some misc changes to the code that runs behind the UI.
This commit is contained in:
OxygenCobalt 2021-01-24 11:48:55 -07:00
parent a5100b31ab
commit 3851c59f4b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
16 changed files with 92 additions and 106 deletions

View file

@ -55,7 +55,6 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
// Kotlin // Kotlin
//noinspection DifferentStdlibGradleVersion
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View file

@ -20,7 +20,7 @@ import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.Accent import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.fixAnimationInfoMemoryLeak import org.oxycblt.auxio.ui.fixAnimInfoLeak
import org.oxycblt.auxio.ui.isLandscape import org.oxycblt.auxio.ui.isLandscape
import org.oxycblt.auxio.ui.isTablet import org.oxycblt.auxio.ui.isTablet
import org.oxycblt.auxio.ui.toColor import org.oxycblt.auxio.ui.toColor
@ -115,7 +115,7 @@ class MainFragment : Fragment() {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
fixAnimationInfoMemoryLeak() fixAnimInfoLeak()
} }
/** /**

View file

@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.fixAnimationInfoMemoryLeak import org.oxycblt.auxio.ui.fixAnimInfoLeak
import org.oxycblt.auxio.ui.isLandscape import org.oxycblt.auxio.ui.isLandscape
import org.oxycblt.auxio.ui.memberBinding import org.oxycblt.auxio.ui.memberBinding
@ -25,9 +25,7 @@ import org.oxycblt.auxio.ui.memberBinding
abstract class DetailFragment : Fragment() { abstract class DetailFragment : Fragment() {
protected val detailModel: DetailViewModel by activityViewModels() protected val detailModel: DetailViewModel by activityViewModels()
protected val playbackModel: PlaybackViewModel by activityViewModels() protected val playbackModel: PlaybackViewModel by activityViewModels()
protected val binding: FragmentDetailBinding by memberBinding( protected val binding by memberBinding(FragmentDetailBinding::inflate)
FragmentDetailBinding::inflate
)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback) requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
@ -48,7 +46,7 @@ abstract class DetailFragment : Fragment() {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
fixAnimationInfoMemoryLeak() fixAnimInfoLeak()
} }
/** /**

View file

@ -20,7 +20,6 @@ import org.oxycblt.auxio.music.processing.MusicLoader
/** /**
* An intermediary [Fragment] that asks for the READ_EXTERNAL_STORAGE permission and runs * An intermediary [Fragment] that asks for the READ_EXTERNAL_STORAGE permission and runs
* the music loading process in the background. * the music loading process in the background.
* FIXME: Leak that occurs when skipping load
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class LoadingFragment : Fragment(R.layout.fragment_loading) { class LoadingFragment : Fragment(R.layout.fragment_loading) {

View file

@ -29,9 +29,7 @@ import org.oxycblt.auxio.ui.memberBinding
class CompactPlaybackFragment : Fragment() { class CompactPlaybackFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val binding: FragmentCompactPlaybackBinding by memberBinding( private val binding by memberBinding(FragmentCompactPlaybackBinding::inflate)
FragmentCompactPlaybackBinding::inflate
)
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,

View file

@ -19,7 +19,7 @@ import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.ui.Accent import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.memberBinding import org.oxycblt.auxio.ui.memberBinding
import org.oxycblt.auxio.ui.toColor import org.oxycblt.auxio.ui.toStateList
/** /**
* A [Fragment] that displays more information about the song, along with more media controls. * A [Fragment] that displays more information about the song, along with more media controls.
@ -29,8 +29,7 @@ import org.oxycblt.auxio.ui.toColor
class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val binding: FragmentPlaybackBinding by memberBinding(FragmentPlaybackBinding::inflate) { private val binding by memberBinding(FragmentPlaybackBinding::inflate) {
// Marquee must be disabled on destruction to prevent memory leaks
playbackSong.isSelected = false playbackSong.isSelected = false
} }
@ -40,7 +39,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
} }
private val controlColor: ColorStateList by lazy { private val controlColor: ColorStateList by lazy {
ColorStateList.valueOf(R.color.control_color.toColor(requireContext())) R.color.control_color.toStateList(requireContext())
} }
override fun onCreateView( override fun onCreateView(
@ -49,6 +48,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
// TODO: Add a swipe-to-next-track function using a ViewPager // TODO: Add a swipe-to-next-track function using a ViewPager
// Would require writing my own variant though to avoid index updates
val normalTextColor = binding.playbackDurationCurrent.currentTextColor val normalTextColor = binding.playbackDurationCurrent.currentTextColor

View file

@ -240,7 +240,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
uploadMetadataToSession(it) uploadMetadataToSession(it)
notification.setMetadata(this, it, settingsManager.colorizeNotif) { notification.setMetadata(this, it, settingsManager.colorizeNotif) {
startForegroundOrNotify("Song") startForegroundOrNotify()
} }
return return
@ -254,7 +254,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
override fun onModeUpdate(mode: PlaybackMode) { override fun onModeUpdate(mode: PlaybackMode) {
notification.updateMode(this) notification.updateMode(this)
startForegroundOrNotify("Mode") startForegroundOrNotify()
} }
override fun onPlayingUpdate(isPlaying: Boolean) { override fun onPlayingUpdate(isPlaying: Boolean) {
@ -262,13 +262,13 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
player.play() player.play()
notification.updatePlaying(this) notification.updatePlaying(this)
audioFocusManager.requestFocus() audioFocusManager.requestFocus()
startForegroundOrNotify("Play") startForegroundOrNotify()
startPollingPosition() startPollingPosition()
} else { } else {
player.pause() player.pause()
notification.updatePlaying(this) notification.updatePlaying(this)
startForegroundOrNotify("Pause") startForegroundOrNotify()
} }
} }
@ -283,18 +283,18 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
} }
notification.updateExtraAction(this, settingsManager.useAltNotifAction) notification.updateExtraAction(this, settingsManager.useAltNotifAction)
startForegroundOrNotify("Loop") startForegroundOrNotify()
} }
override fun onShuffleUpdate(isShuffling: Boolean) { override fun onShuffleUpdate(isShuffling: Boolean) {
if (settingsManager.useAltNotifAction) { if (settingsManager.useAltNotifAction) {
notification.updateExtraAction(this, settingsManager.useAltNotifAction) notification.updateExtraAction(this, settingsManager.useAltNotifAction)
startForegroundOrNotify("Shuffle update") startForegroundOrNotify()
} }
} }
override fun onSeekConfirm(position: Long) { override fun onSeek(position: Long) {
player.seekTo(position) player.seekTo(position)
} }
@ -309,7 +309,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
override fun onColorizeNotifUpdate(doColorize: Boolean) { override fun onColorizeNotifUpdate(doColorize: Boolean) {
playbackManager.song?.let { playbackManager.song?.let {
notification.setMetadata(this, it, settingsManager.colorizeNotif) { notification.setMetadata(this, it, settingsManager.colorizeNotif) {
startForegroundOrNotify("Colorize update") startForegroundOrNotify()
} }
} }
} }
@ -317,13 +317,13 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
override fun onNotifActionUpdate(useAltAction: Boolean) { override fun onNotifActionUpdate(useAltAction: Boolean) {
notification.updateExtraAction(this, useAltAction) notification.updateExtraAction(this, useAltAction)
startForegroundOrNotify("Notif action update") startForegroundOrNotify()
} }
override fun onShowCoverUpdate(showCovers: Boolean) { override fun onShowCoverUpdate(showCovers: Boolean) {
playbackManager.song?.let { playbackManager.song?.let {
notification.setMetadata(this, it, settingsManager.colorizeNotif) { notification.setMetadata(this, it, settingsManager.colorizeNotif) {
startForegroundOrNotify("Cover update") startForegroundOrNotify()
} }
} }
} }
@ -331,7 +331,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
override fun onQualityCoverUpdate(doQualityCovers: Boolean) { override fun onQualityCoverUpdate(doQualityCovers: Boolean) {
playbackManager.song?.let { song -> playbackManager.song?.let { song ->
notification.setMetadata(this, song, settingsManager.colorizeNotif) { notification.setMetadata(this, song, settingsManager.colorizeNotif) {
startForegroundOrNotify("Quality cover update") startForegroundOrNotify()
} }
} }
} }
@ -388,7 +388,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
playbackManager.song?.let { playbackManager.song?.let {
notification.setMetadata(this, it, settingsManager.colorizeNotif) { notification.setMetadata(this, it, settingsManager.colorizeNotif) {
if (playbackManager.isPlaying) { if (playbackManager.isPlaying) {
startForegroundOrNotify("Restore") startForegroundOrNotify()
} else { } else {
stopForegroundAndNotification() stopForegroundAndNotification()
} }
@ -437,15 +437,14 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
/** /**
* Bring the service into the foreground and show the notification, or refresh the notification. * Bring the service into the foreground and show the notification, or refresh the notification.
* @param reason (Debug) The reason for this call.
*/ */
private fun startForegroundOrNotify(reason: String) { private fun startForegroundOrNotify() {
// Don't start foreground if: // Don't start foreground if:
// - The playback hasnt even started // - The playback hasnt even started
// - The playback hasnt been restored // - The playback hasnt been restored
// - There is nothing to play // - There is nothing to play
if (playbackManager.hasPlayed && playbackManager.isRestored && playbackManager.song != null) { if (playbackManager.hasPlayed && playbackManager.isRestored && playbackManager.song != null) {
logD("Starting foreground/notifying because of $reason") logD("Starting foreground/notifying")
if (!isForeground) { if (!isForeground) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@ -484,7 +483,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
// Play/Pause if any of the keys are play/pause // Play/Pause if any of the keys are play/pause
KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY,
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> { KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> {
playbackManager.setPlayingStatus(!playbackManager.isPlaying) playbackManager.setPlaying(!playbackManager.isPlaying)
true true
} }
@ -554,10 +553,9 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
private fun onGain() { private fun onGain() {
if (settingsManager.doAudioFocus) { if (settingsManager.doAudioFocus) {
if (player.volume == VOLUME_DUCK && playbackManager.isPlaying) { if (player.volume == VOLUME_DUCK && playbackManager.isPlaying) {
player.volume = VOLUME_DUCK unduck()
animateVolume(VOLUME_DUCK, VOLUME_FULL)
} else if (pauseWasFromAudioFocus) { } else if (pauseWasFromAudioFocus) {
playbackManager.setPlayingStatus(true) playbackManager.setPlaying(true)
} }
pauseWasFromAudioFocus = false pauseWasFromAudioFocus = false
@ -567,7 +565,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
private fun onLoss() { private fun onLoss() {
if (settingsManager.doAudioFocus && playbackManager.isPlaying) { if (settingsManager.doAudioFocus && playbackManager.isPlaying) {
pauseWasFromAudioFocus = true pauseWasFromAudioFocus = true
playbackManager.setPlayingStatus(false) playbackManager.setPlaying(false)
} }
} }
@ -577,14 +575,16 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
} }
} }
private fun animateVolume(from: Float, to: Float) { private fun unduck() {
player.volume = VOLUME_DUCK
ValueAnimator().apply { ValueAnimator().apply {
setFloatValues(from, to) setFloatValues(VOLUME_DUCK, VOLUME_FULL)
duration = DUCK_DURATION duration = DUCK_DURATION
addListener( addListener(
onStart = { player.volume = from }, onStart = { player.volume = VOLUME_DUCK },
onCancel = { player.volume = to }, onCancel = { player.volume = VOLUME_FULL },
onEnd = { player.volume = to } onEnd = { player.volume = VOLUME_FULL }
) )
addUpdateListener { addUpdateListener {
player.volume = it.animatedValue as Float player.volume = it.animatedValue as Float
@ -609,7 +609,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true) playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true)
NotificationUtils.ACTION_SKIP_PREV -> playbackManager.prev() NotificationUtils.ACTION_SKIP_PREV -> playbackManager.prev()
NotificationUtils.ACTION_PLAY_PAUSE -> { NotificationUtils.ACTION_PLAY_PAUSE -> {
playbackManager.setPlayingStatus(!playbackManager.isPlaying) playbackManager.setPlaying(!playbackManager.isPlaying)
} }
NotificationUtils.ACTION_SKIP_NEXT -> playbackManager.next() NotificationUtils.ACTION_SKIP_NEXT -> playbackManager.next()
NotificationUtils.ACTION_EXIT -> stop() NotificationUtils.ACTION_EXIT -> stop()
@ -643,7 +643,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
if (playbackManager.song != null && settingsManager.doPlugMgt) { if (playbackManager.song != null && settingsManager.doPlugMgt) {
logD("Device connected, resuming...") logD("Device connected, resuming...")
playbackManager.setPlayingStatus(true) playbackManager.setPlaying(true)
} }
} }
@ -654,7 +654,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
if (playbackManager.song != null && settingsManager.doPlugMgt) { if (playbackManager.song != null && settingsManager.doPlugMgt) {
logD("Device disconnected, pausing...") logD("Device disconnected, pausing...")
playbackManager.setPlayingStatus(false) playbackManager.setPlaying(false)
} }
} }
@ -662,7 +662,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
* Stop if the X button was clicked from the notification * Stop if the X button was clicked from the notification
*/ */
private fun stop() { private fun stop() {
playbackManager.setPlayingStatus(false) playbackManager.setPlaying(false)
stopForegroundAndNotification() stopForegroundAndNotification()
} }
} }

View file

@ -281,7 +281,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
fun invertPlayingStatus() { fun invertPlayingStatus() {
enableAnimation() enableAnimation()
playbackManager.setPlayingStatus(!playbackManager.isPlaying) playbackManager.setPlaying(!playbackManager.isPlaying)
} }
/** Flip the shuffle status, e.g from on to off. Will keep song by default. */ /** Flip the shuffle status, e.g from on to off. Will keep song by default. */

View file

@ -59,9 +59,7 @@ class QueueFragment : Fragment() {
insets.systemWindowInsetTop insets.systemWindowInsetTop
} }
(parent as View).updatePadding( (parent as View).updatePadding(top = top)
top = top
)
insets insets
} }
@ -77,7 +75,7 @@ class QueueFragment : Fragment() {
helper.attachToRecyclerView(this) helper.attachToRecyclerView(this)
} }
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ----
playbackModel.userQueue.observe(viewLifecycleOwner) { playbackModel.userQueue.observe(viewLifecycleOwner) {
if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) { if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {

View file

@ -187,11 +187,7 @@ class PlaybackStateManager private constructor() {
resetLoopMode() resetLoopMode()
updatePlayback(song) updatePlayback(song)
// Depending on the configuration, keep the shuffle mode on.
setShuffling(settingsManager.keepShuffle && mIsShuffling, keepSong = true) setShuffling(settingsManager.keepShuffle && mIsShuffling, keepSong = true)
mIndex = mQueue.indexOf(song)
} }
/** /**
@ -246,7 +242,7 @@ class PlaybackStateManager private constructor() {
mPosition = 0 mPosition = 0
if (!mIsPlaying) { if (!mIsPlaying) {
setPlayingStatus(true) setPlaying(true)
} }
} }
@ -265,14 +261,15 @@ class PlaybackStateManager private constructor() {
} }
/** /**
* **Seek** to a position, this calls [PlaybackStateManager.Callback.onSeekConfirm] to notify * **Seek** to a position, this calls [PlaybackStateManager.Callback.onSeek] to notify
* elements that rely on that. * elements that rely on that.
* @param position The position to seek to in millis. * @param position The position to seek to in millis.
* @see setPosition
*/ */
fun seekTo(position: Long) { fun seekTo(position: Long) {
mPosition = position mPosition = position
callbacks.forEach { it.onSeekConfirm(position) } callbacks.forEach { it.onSeek(position) }
} }
// --- QUEUE FUNCTIONS --- // --- QUEUE FUNCTIONS ---
@ -340,13 +337,9 @@ class PlaybackStateManager private constructor() {
mIndex = 0 mIndex = 0
forceQueueUpdate() forceQueueUpdate()
// The whole point here is making the playback pause and loop, so duplicate
// the updatePlayback code instead of using it with a useless arg tacked on.
mSong = mQueue[0] mSong = mQueue[0]
mPosition = 0 mPosition = 0
setPlaying(false)
setPlayingStatus(false)
mIsInUserQueue = false mIsInUserQueue = false
} }
@ -505,11 +498,11 @@ class PlaybackStateManager private constructor() {
/** /**
* Set the shuffle status. Updates the queue accordingly * Set the shuffle status. Updates the queue accordingly
* @param value Whether the queue should be shuffled or not. * @param shuffling Whether the queue should be shuffled or not.
* @param keepSong Whether the current song should be kept as the queue is shuffled/unshuffled * @param keepSong Whether the current song should be kept as the queue is shuffled/unshuffled
*/ */
fun setShuffling(value: Boolean, keepSong: Boolean) { fun setShuffling(shuffling: Boolean, keepSong: Boolean) {
mIsShuffling = value mIsShuffling = shuffling
if (mIsShuffling) { if (mIsShuffling) {
genShuffle(keepSong, mIsInUserQueue) genShuffle(keepSong, mIsInUserQueue)
@ -524,10 +517,7 @@ class PlaybackStateManager private constructor() {
* @param useLastSong Whether to use the last song in the queue instead of the current one * @param useLastSong Whether to use the last song in the queue instead of the current one
* @return A new shuffled queue * @return A new shuffled queue
*/ */
private fun genShuffle( private fun genShuffle(keepSong: Boolean, useLastSong: Boolean) {
keepSong: Boolean,
useLastSong: Boolean
) {
val lastSong = if (useLastSong) mQueue[0] else mSong val lastSong = if (useLastSong) mQueue[0] else mSong
logD("Shuffling queue") logD("Shuffling queue")
@ -551,10 +541,7 @@ class PlaybackStateManager private constructor() {
* @param keepSong Whether the current song should be kept as the queue is unshuffled * @param keepSong Whether the current song should be kept as the queue is unshuffled
* @param useLastSong Whether to use the previous song for the index calculations. * @param useLastSong Whether to use the previous song for the index calculations.
*/ */
private fun resetShuffle( private fun resetShuffle(keepSong: Boolean, useLastSong: Boolean) {
keepSong: Boolean,
useLastSong: Boolean
) {
val lastSong = if (useLastSong) mQueue[mIndex] else mSong val lastSong = if (useLastSong) mQueue[mIndex] else mSong
mQueue = when (mMode) { mQueue = when (mMode) {
@ -575,15 +562,15 @@ class PlaybackStateManager private constructor() {
/** /**
* Set the current playing status * Set the current playing status
* @param value Whether the playback should be playing or paused. * @param playing Whether the playback should be playing or paused.
*/ */
fun setPlayingStatus(value: Boolean) { fun setPlaying(playing: Boolean) {
if (mIsPlaying != value) { if (mIsPlaying != playing) {
if (value) { if (playing) {
mHasPlayed = true mHasPlayed = true
} }
mIsPlaying = value mIsPlaying = playing
} }
} }
@ -592,7 +579,7 @@ class PlaybackStateManager private constructor() {
*/ */
fun rewind() { fun rewind() {
seekTo(0) seekTo(0)
setPlayingStatus(true) setPlaying(true)
} }
/** /**
@ -724,7 +711,7 @@ class PlaybackStateManager private constructor() {
mIndex = playbackState.index mIndex = playbackState.index
callbacks.forEach { callbacks.forEach {
it.onSeekConfirm(mPosition) it.onSeek(mPosition)
it.onModeUpdate(mMode) it.onModeUpdate(mMode)
it.onRestoreFinish() it.onRestoreFinish()
} }
@ -839,7 +826,7 @@ class PlaybackStateManager private constructor() {
fun onPlayingUpdate(isPlaying: Boolean) {} fun onPlayingUpdate(isPlaying: Boolean) {}
fun onShuffleUpdate(isShuffling: Boolean) {} fun onShuffleUpdate(isShuffling: Boolean) {}
fun onLoopUpdate(mode: LoopMode) {} fun onLoopUpdate(mode: LoopMode) {}
fun onSeekConfirm(position: Long) {} fun onSeek(position: Long) {}
fun onInUserQueueUpdate(isInUserQueue: Boolean) {} fun onInUserQueueUpdate(isInUserQueue: Boolean) {}
fun onRestoreFinish() {} fun onRestoreFinish() {}
} }

View file

@ -24,10 +24,11 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.Accent import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.fixAnimationInfoMemoryLeak import org.oxycblt.auxio.ui.fixAnimInfoLeak
import org.oxycblt.auxio.ui.getSpans import org.oxycblt.auxio.ui.getSpans
import org.oxycblt.auxio.ui.requireCompatActivity import org.oxycblt.auxio.ui.requireCompatActivity
import org.oxycblt.auxio.ui.toColor import org.oxycblt.auxio.ui.toColor
import org.oxycblt.auxio.ui.toStateList
/** /**
* A [Fragment] that allows for the searching of the entire music library. * A [Fragment] that allows for the searching of the entire music library.
@ -73,9 +74,7 @@ class SearchFragment : Fragment() {
binding.searchTextLayout.apply { binding.searchTextLayout.apply {
boxStrokeColor = accent boxStrokeColor = accent
hintTextColor = ColorStateList.valueOf(accent) hintTextColor = ColorStateList.valueOf(accent)
setEndIconTintList( setEndIconTintList(R.color.control_color.toStateList(context))
ColorStateList.valueOf(R.color.control_color.toColor(requireContext()))
)
} }
binding.searchEditText.addTextChangedListener { binding.searchEditText.addTextChangedListener {
@ -133,7 +132,7 @@ class SearchFragment : Fragment() {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
fixAnimationInfoMemoryLeak() fixAnimInfoLeak()
} }
override fun onResume() { override fun onResume() {

View file

@ -37,6 +37,11 @@ class SearchViewModel : ViewModel() {
mFilterMode = settingsManager.searchFilterMode mFilterMode = settingsManager.searchFilterMode
} }
/**
* Perform a search of the music library. Will push results to [searchResults].
* @param query The query to use
* @param context [Context] required to create the headers
*/
fun doSearch(query: String, context: Context) { fun doSearch(query: String, context: Context) {
mLastQuery = query mLastQuery = query

View file

@ -53,9 +53,7 @@ data class Accent(
/** /**
* Get a [ColorStateList] of the accent * Get a [ColorStateList] of the accent
*/ */
fun getStateList(context: Context): ColorStateList { fun getStateList(context: Context): ColorStateList = color.toStateList(context)
return ColorStateList.valueOf(color.toColor(context))
}
/** /**
* Get the name (in bold) and the hex value of a accent. * Get the name (in bold) and the hex value of a accent.

View file

@ -83,7 +83,8 @@ fun String.createToast(context: Context) {
} }
/** /**
* Require an [AppCompatActivity] * Ensure that a not-null [AppCompatActivity] will be returned.
* @throws IllegalStateException When there is no activity or if the activity is null
*/ */
fun Fragment.requireCompatActivity(): AppCompatActivity { fun Fragment.requireCompatActivity(): AppCompatActivity {
val activity = requireActivity() val activity = requireActivity()
@ -122,6 +123,13 @@ fun Int.toColor(context: Context): Int {
} }
} }
/**
* Resolve a color and turn it into a [ColorStateList]
* @param context [Context] required
* @return The resolved color as a [ColorStateList]
*/
fun Int.toStateList(context: Context): ColorStateList = ColorStateList.valueOf(toColor(context))
// --- CONFIGURATION --- // --- CONFIGURATION ---
/** /**
@ -209,16 +217,14 @@ private fun isSystemBarOnBottom(activity: Activity): Boolean {
// --- HACKY NIGHTMARES --- // --- HACKY NIGHTMARES ---
/** /**
* Use ***R E F L E C T I O N*** to fix a memory leak where mAnimationInfo will keep a reference to * Use reflection to fix a memory leak in the [Fragment] source code where the focused view will
* its focused view. * never be cleared. I can't believe I have to do this.
*
* I can't believe I have to do this.
*/ */
fun Fragment.fixAnimationInfoMemoryLeak() { fun Fragment.fixAnimInfoLeak() {
try { try {
Fragment::class.java.getDeclaredMethod("setFocusedView", View::class.java).let { Fragment::class.java.getDeclaredMethod("setFocusedView", View::class.java).apply {
it.isAccessible = true isAccessible = true
it.invoke(this, null) invoke(this@fixAnimInfoLeak, null)
} }
} catch (e: Exception) { } catch (e: Exception) {
logE("mAnimationInfo leak fix failed.") logE("mAnimationInfo leak fix failed.")

View file

@ -2,32 +2,31 @@ package org.oxycblt.auxio.ui
import android.os.Looper import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
/** /**
* A delegate that creates a binding that can be used as a member variable without nullability or * A delegate that creates a binding that can be used as a member variable without nullability or
* memory leaks. * memory leaks.
* @param bindingFactory The ViewBinding inflation method that should be used * @param inflate The ViewBinding inflation method that should be used
* @param onDestroy Any code that should be run when the binding is destroyed
*/ */
fun <T : ViewBinding> Fragment.memberBinding( fun <T : ViewDataBinding> Fragment.memberBinding(
bindingFactory: (LayoutInflater) -> T, inflate: (LayoutInflater) -> T,
onDestroy: T.() -> Unit = {} onDestroy: T.() -> Unit = {}
) = FragmentBinderDelegate(this, bindingFactory, onDestroy) ) = MemberBinder(this, inflate, onDestroy)
/** /**
* The delegate for the [memberBinding] shortcut function. * The delegate for the [memberBinding] shortcut function.
* Adapted from KAHelpers (https://github.com/FunkyMuse/KAHelpers/tree/master/viewbinding) * Adapted from KAHelpers (https://github.com/FunkyMuse/KAHelpers/tree/master/viewbinding)
*/ */
class FragmentBinderDelegate<T : ViewBinding>( class MemberBinder<T : ViewDataBinding>(
private val fragment: Fragment, private val fragment: Fragment,
private val inflate: (LayoutInflater) -> T, private val inflate: (LayoutInflater) -> T,
private val onDestroy: T.() -> Unit private val onDestroy: T.() -> Unit
@ -36,7 +35,7 @@ class FragmentBinderDelegate<T : ViewBinding>(
init { init {
fragment.observeOwnerThroughCreation { fragment.observeOwnerThroughCreation {
lifecycle.addObserver(this@FragmentBinderDelegate) lifecycle.addObserver(this@MemberBinder)
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 163 KiB