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"])
|
||||
|
||||
// Kotlin
|
||||
//noinspection DifferentStdlibGradleVersion
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$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.playback.PlaybackViewModel
|
||||
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.isTablet
|
||||
import org.oxycblt.auxio.ui.toColor
|
||||
|
@ -115,7 +115,7 @@ class MainFragment : Fragment() {
|
|||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
||||
fixAnimationInfoMemoryLeak()
|
||||
fixAnimInfoLeak()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import org.oxycblt.auxio.databinding.FragmentDetailBinding
|
||||
import org.oxycblt.auxio.music.BaseModel
|
||||
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.memberBinding
|
||||
|
||||
|
@ -25,9 +25,7 @@ import org.oxycblt.auxio.ui.memberBinding
|
|||
abstract class DetailFragment : Fragment() {
|
||||
protected val detailModel: DetailViewModel by activityViewModels()
|
||||
protected val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
protected val binding: FragmentDetailBinding by memberBinding(
|
||||
FragmentDetailBinding::inflate
|
||||
)
|
||||
protected val binding by memberBinding(FragmentDetailBinding::inflate)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
|
||||
|
@ -48,7 +46,7 @@ abstract class DetailFragment : Fragment() {
|
|||
override fun 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
|
||||
* the music loading process in the background.
|
||||
* FIXME: Leak that occurs when skipping load
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class LoadingFragment : Fragment(R.layout.fragment_loading) {
|
||||
|
|
|
@ -29,9 +29,7 @@ import org.oxycblt.auxio.ui.memberBinding
|
|||
class CompactPlaybackFragment : Fragment() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
private val binding: FragmentCompactPlaybackBinding by memberBinding(
|
||||
FragmentCompactPlaybackBinding::inflate
|
||||
)
|
||||
private val binding by memberBinding(FragmentCompactPlaybackBinding::inflate)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
|
|
@ -19,7 +19,7 @@ import org.oxycblt.auxio.logD
|
|||
import org.oxycblt.auxio.playback.state.LoopMode
|
||||
import org.oxycblt.auxio.ui.Accent
|
||||
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.
|
||||
|
@ -29,8 +29,7 @@ import org.oxycblt.auxio.ui.toColor
|
|||
class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
private val binding: FragmentPlaybackBinding by memberBinding(FragmentPlaybackBinding::inflate) {
|
||||
// Marquee must be disabled on destruction to prevent memory leaks
|
||||
private val binding by memberBinding(FragmentPlaybackBinding::inflate) {
|
||||
playbackSong.isSelected = false
|
||||
}
|
||||
|
||||
|
@ -40,7 +39,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
}
|
||||
|
||||
private val controlColor: ColorStateList by lazy {
|
||||
ColorStateList.valueOf(R.color.control_color.toColor(requireContext()))
|
||||
R.color.control_color.toStateList(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -49,6 +48,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
// 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
|
||||
|
||||
|
|
|
@ -240,7 +240,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
uploadMetadataToSession(it)
|
||||
|
||||
notification.setMetadata(this, it, settingsManager.colorizeNotif) {
|
||||
startForegroundOrNotify("Song")
|
||||
startForegroundOrNotify()
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -254,7 +254,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
override fun onModeUpdate(mode: PlaybackMode) {
|
||||
notification.updateMode(this)
|
||||
|
||||
startForegroundOrNotify("Mode")
|
||||
startForegroundOrNotify()
|
||||
}
|
||||
|
||||
override fun onPlayingUpdate(isPlaying: Boolean) {
|
||||
|
@ -262,13 +262,13 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
player.play()
|
||||
notification.updatePlaying(this)
|
||||
audioFocusManager.requestFocus()
|
||||
startForegroundOrNotify("Play")
|
||||
startForegroundOrNotify()
|
||||
|
||||
startPollingPosition()
|
||||
} else {
|
||||
player.pause()
|
||||
notification.updatePlaying(this)
|
||||
startForegroundOrNotify("Pause")
|
||||
startForegroundOrNotify()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,18 +283,18 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
}
|
||||
|
||||
notification.updateExtraAction(this, settingsManager.useAltNotifAction)
|
||||
startForegroundOrNotify("Loop")
|
||||
startForegroundOrNotify()
|
||||
}
|
||||
|
||||
override fun onShuffleUpdate(isShuffling: Boolean) {
|
||||
if (settingsManager.useAltNotifAction) {
|
||||
notification.updateExtraAction(this, settingsManager.useAltNotifAction)
|
||||
|
||||
startForegroundOrNotify("Shuffle update")
|
||||
startForegroundOrNotify()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSeekConfirm(position: Long) {
|
||||
override fun onSeek(position: Long) {
|
||||
player.seekTo(position)
|
||||
}
|
||||
|
||||
|
@ -309,7 +309,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
override fun onColorizeNotifUpdate(doColorize: Boolean) {
|
||||
playbackManager.song?.let {
|
||||
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) {
|
||||
notification.updateExtraAction(this, useAltAction)
|
||||
|
||||
startForegroundOrNotify("Notif action update")
|
||||
startForegroundOrNotify()
|
||||
}
|
||||
|
||||
override fun onShowCoverUpdate(showCovers: Boolean) {
|
||||
playbackManager.song?.let {
|
||||
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) {
|
||||
playbackManager.song?.let { song ->
|
||||
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 {
|
||||
notification.setMetadata(this, it, settingsManager.colorizeNotif) {
|
||||
if (playbackManager.isPlaying) {
|
||||
startForegroundOrNotify("Restore")
|
||||
startForegroundOrNotify()
|
||||
} else {
|
||||
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.
|
||||
* @param reason (Debug) The reason for this call.
|
||||
*/
|
||||
private fun startForegroundOrNotify(reason: String) {
|
||||
private fun startForegroundOrNotify() {
|
||||
// Don't start foreground if:
|
||||
// - The playback hasnt even started
|
||||
// - The playback hasnt been restored
|
||||
// - There is nothing to play
|
||||
if (playbackManager.hasPlayed && playbackManager.isRestored && playbackManager.song != null) {
|
||||
logD("Starting foreground/notifying because of $reason")
|
||||
logD("Starting foreground/notifying")
|
||||
|
||||
if (!isForeground) {
|
||||
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
|
||||
KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY,
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> {
|
||||
playbackManager.setPlayingStatus(!playbackManager.isPlaying)
|
||||
playbackManager.setPlaying(!playbackManager.isPlaying)
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -554,10 +553,9 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
private fun onGain() {
|
||||
if (settingsManager.doAudioFocus) {
|
||||
if (player.volume == VOLUME_DUCK && playbackManager.isPlaying) {
|
||||
player.volume = VOLUME_DUCK
|
||||
animateVolume(VOLUME_DUCK, VOLUME_FULL)
|
||||
unduck()
|
||||
} else if (pauseWasFromAudioFocus) {
|
||||
playbackManager.setPlayingStatus(true)
|
||||
playbackManager.setPlaying(true)
|
||||
}
|
||||
|
||||
pauseWasFromAudioFocus = false
|
||||
|
@ -567,7 +565,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
private fun onLoss() {
|
||||
if (settingsManager.doAudioFocus && playbackManager.isPlaying) {
|
||||
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 {
|
||||
setFloatValues(from, to)
|
||||
setFloatValues(VOLUME_DUCK, VOLUME_FULL)
|
||||
duration = DUCK_DURATION
|
||||
addListener(
|
||||
onStart = { player.volume = from },
|
||||
onCancel = { player.volume = to },
|
||||
onEnd = { player.volume = to }
|
||||
onStart = { player.volume = VOLUME_DUCK },
|
||||
onCancel = { player.volume = VOLUME_FULL },
|
||||
onEnd = { player.volume = VOLUME_FULL }
|
||||
)
|
||||
addUpdateListener {
|
||||
player.volume = it.animatedValue as Float
|
||||
|
@ -609,7 +609,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true)
|
||||
NotificationUtils.ACTION_SKIP_PREV -> playbackManager.prev()
|
||||
NotificationUtils.ACTION_PLAY_PAUSE -> {
|
||||
playbackManager.setPlayingStatus(!playbackManager.isPlaying)
|
||||
playbackManager.setPlaying(!playbackManager.isPlaying)
|
||||
}
|
||||
NotificationUtils.ACTION_SKIP_NEXT -> playbackManager.next()
|
||||
NotificationUtils.ACTION_EXIT -> stop()
|
||||
|
@ -643,7 +643,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
|||
if (playbackManager.song != null && settingsManager.doPlugMgt) {
|
||||
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) {
|
||||
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
|
||||
*/
|
||||
private fun stop() {
|
||||
playbackManager.setPlayingStatus(false)
|
||||
playbackManager.setPlaying(false)
|
||||
stopForegroundAndNotification()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -281,7 +281,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
fun invertPlayingStatus() {
|
||||
enableAnimation()
|
||||
|
||||
playbackManager.setPlayingStatus(!playbackManager.isPlaying)
|
||||
playbackManager.setPlaying(!playbackManager.isPlaying)
|
||||
}
|
||||
|
||||
/** Flip the shuffle status, e.g from on to off. Will keep song by default. */
|
||||
|
|
|
@ -59,9 +59,7 @@ class QueueFragment : Fragment() {
|
|||
insets.systemWindowInsetTop
|
||||
}
|
||||
|
||||
(parent as View).updatePadding(
|
||||
top = top
|
||||
)
|
||||
(parent as View).updatePadding(top = top)
|
||||
|
||||
insets
|
||||
}
|
||||
|
@ -77,7 +75,7 @@ class QueueFragment : Fragment() {
|
|||
helper.attachToRecyclerView(this)
|
||||
}
|
||||
|
||||
// --- VIEWMODEL SETUP ---
|
||||
// --- VIEWMODEL SETUP ----
|
||||
|
||||
playbackModel.userQueue.observe(viewLifecycleOwner) {
|
||||
if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
|
||||
|
|
|
@ -187,11 +187,7 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
resetLoopMode()
|
||||
updatePlayback(song)
|
||||
|
||||
// Depending on the configuration, keep the shuffle mode on.
|
||||
setShuffling(settingsManager.keepShuffle && mIsShuffling, keepSong = true)
|
||||
|
||||
mIndex = mQueue.indexOf(song)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,7 +242,7 @@ class PlaybackStateManager private constructor() {
|
|||
mPosition = 0
|
||||
|
||||
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.
|
||||
* @param position The position to seek to in millis.
|
||||
* @see setPosition
|
||||
*/
|
||||
fun seekTo(position: Long) {
|
||||
mPosition = position
|
||||
|
||||
callbacks.forEach { it.onSeekConfirm(position) }
|
||||
callbacks.forEach { it.onSeek(position) }
|
||||
}
|
||||
|
||||
// --- QUEUE FUNCTIONS ---
|
||||
|
@ -340,13 +337,9 @@ class PlaybackStateManager private constructor() {
|
|||
mIndex = 0
|
||||
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]
|
||||
mPosition = 0
|
||||
|
||||
setPlayingStatus(false)
|
||||
|
||||
setPlaying(false)
|
||||
mIsInUserQueue = false
|
||||
}
|
||||
|
||||
|
@ -505,11 +498,11 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun setShuffling(value: Boolean, keepSong: Boolean) {
|
||||
mIsShuffling = value
|
||||
fun setShuffling(shuffling: Boolean, keepSong: Boolean) {
|
||||
mIsShuffling = shuffling
|
||||
|
||||
if (mIsShuffling) {
|
||||
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
|
||||
* @return A new shuffled queue
|
||||
*/
|
||||
private fun genShuffle(
|
||||
keepSong: Boolean,
|
||||
useLastSong: Boolean
|
||||
) {
|
||||
private fun genShuffle(keepSong: Boolean, useLastSong: Boolean) {
|
||||
val lastSong = if (useLastSong) mQueue[0] else mSong
|
||||
|
||||
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 useLastSong Whether to use the previous song for the index calculations.
|
||||
*/
|
||||
private fun resetShuffle(
|
||||
keepSong: Boolean,
|
||||
useLastSong: Boolean
|
||||
) {
|
||||
private fun resetShuffle(keepSong: Boolean, useLastSong: Boolean) {
|
||||
val lastSong = if (useLastSong) mQueue[mIndex] else mSong
|
||||
|
||||
mQueue = when (mMode) {
|
||||
|
@ -575,15 +562,15 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
/**
|
||||
* 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) {
|
||||
if (mIsPlaying != value) {
|
||||
if (value) {
|
||||
fun setPlaying(playing: Boolean) {
|
||||
if (mIsPlaying != playing) {
|
||||
if (playing) {
|
||||
mHasPlayed = true
|
||||
}
|
||||
|
||||
mIsPlaying = value
|
||||
mIsPlaying = playing
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -592,7 +579,7 @@ class PlaybackStateManager private constructor() {
|
|||
*/
|
||||
fun rewind() {
|
||||
seekTo(0)
|
||||
setPlayingStatus(true)
|
||||
setPlaying(true)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -724,7 +711,7 @@ class PlaybackStateManager private constructor() {
|
|||
mIndex = playbackState.index
|
||||
|
||||
callbacks.forEach {
|
||||
it.onSeekConfirm(mPosition)
|
||||
it.onSeek(mPosition)
|
||||
it.onModeUpdate(mMode)
|
||||
it.onRestoreFinish()
|
||||
}
|
||||
|
@ -839,7 +826,7 @@ class PlaybackStateManager private constructor() {
|
|||
fun onPlayingUpdate(isPlaying: Boolean) {}
|
||||
fun onShuffleUpdate(isShuffling: Boolean) {}
|
||||
fun onLoopUpdate(mode: LoopMode) {}
|
||||
fun onSeekConfirm(position: Long) {}
|
||||
fun onSeek(position: Long) {}
|
||||
fun onInUserQueueUpdate(isInUserQueue: Boolean) {}
|
||||
fun onRestoreFinish() {}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,11 @@ import org.oxycblt.auxio.music.Song
|
|||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.ui.Accent
|
||||
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.requireCompatActivity
|
||||
import org.oxycblt.auxio.ui.toColor
|
||||
import org.oxycblt.auxio.ui.toStateList
|
||||
|
||||
/**
|
||||
* A [Fragment] that allows for the searching of the entire music library.
|
||||
|
@ -73,9 +74,7 @@ class SearchFragment : Fragment() {
|
|||
binding.searchTextLayout.apply {
|
||||
boxStrokeColor = accent
|
||||
hintTextColor = ColorStateList.valueOf(accent)
|
||||
setEndIconTintList(
|
||||
ColorStateList.valueOf(R.color.control_color.toColor(requireContext()))
|
||||
)
|
||||
setEndIconTintList(R.color.control_color.toStateList(context))
|
||||
}
|
||||
|
||||
binding.searchEditText.addTextChangedListener {
|
||||
|
@ -133,7 +132,7 @@ class SearchFragment : Fragment() {
|
|||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
||||
fixAnimationInfoMemoryLeak()
|
||||
fixAnimInfoLeak()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
|
@ -37,6 +37,11 @@ class SearchViewModel : ViewModel() {
|
|||
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) {
|
||||
mLastQuery = query
|
||||
|
||||
|
|
|
@ -53,9 +53,7 @@ data class Accent(
|
|||
/**
|
||||
* Get a [ColorStateList] of the accent
|
||||
*/
|
||||
fun getStateList(context: Context): ColorStateList {
|
||||
return ColorStateList.valueOf(color.toColor(context))
|
||||
}
|
||||
fun getStateList(context: Context): ColorStateList = color.toStateList(context)
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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 ---
|
||||
|
||||
/**
|
||||
|
@ -209,16 +217,14 @@ private fun isSystemBarOnBottom(activity: Activity): Boolean {
|
|||
// --- 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
|
||||
* its focused view.
|
||||
*
|
||||
* I can't believe I have to do this.
|
||||
* Use reflection to fix a memory leak in the [Fragment] source code where the focused view will
|
||||
* never be cleared. I can't believe I have to do this.
|
||||
*/
|
||||
fun Fragment.fixAnimationInfoMemoryLeak() {
|
||||
fun Fragment.fixAnimInfoLeak() {
|
||||
try {
|
||||
Fragment::class.java.getDeclaredMethod("setFocusedView", View::class.java).let {
|
||||
it.isAccessible = true
|
||||
it.invoke(this, null)
|
||||
Fragment::class.java.getDeclaredMethod("setFocusedView", View::class.java).apply {
|
||||
isAccessible = true
|
||||
invoke(this@fixAnimInfoLeak, null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logE("mAnimationInfo leak fix failed.")
|
||||
|
|
|
@ -2,32 +2,31 @@ package org.oxycblt.auxio.ui
|
|||
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* A delegate that creates a binding that can be used as a member variable without nullability or
|
||||
* memory leaks.
|
||||
* @param bindingFactory The ViewBinding inflation method that should be used
|
||||
* @param onDestroy Any code that should be run when the binding is destroyed
|
||||
* @param inflate The ViewBinding inflation method that should be used
|
||||
*/
|
||||
fun <T : ViewBinding> Fragment.memberBinding(
|
||||
bindingFactory: (LayoutInflater) -> T,
|
||||
fun <T : ViewDataBinding> Fragment.memberBinding(
|
||||
inflate: (LayoutInflater) -> T,
|
||||
onDestroy: T.() -> Unit = {}
|
||||
) = FragmentBinderDelegate(this, bindingFactory, onDestroy)
|
||||
) = MemberBinder(this, inflate, onDestroy)
|
||||
|
||||
/**
|
||||
* The delegate for the [memberBinding] shortcut function.
|
||||
* 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 inflate: (LayoutInflater) -> T,
|
||||
private val onDestroy: T.() -> Unit
|
||||
|
@ -36,7 +35,7 @@ class FragmentBinderDelegate<T : ViewBinding>(
|
|||
|
||||
init {
|
||||
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