Update UI code
Make some misc changes to the code that runs behind the UI.
This commit is contained in:
parent
a5100b31ab
commit
3851c59f4b
16 changed files with 92 additions and 106 deletions
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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. */
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.")
|
||||||
|
|
|
@ -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 |
Loading…
Reference in a new issue