From 3851c59f4b9d31c174468d243591fb7ef2b459ae Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 24 Jan 2021 11:48:55 -0700 Subject: [PATCH] Update UI code Make some misc changes to the code that runs behind the UI. --- app/build.gradle | 1 - .../java/org/oxycblt/auxio/MainFragment.kt | 4 +- .../oxycblt/auxio/detail/DetailFragment.kt | 8 +-- .../oxycblt/auxio/loading/LoadingFragment.kt | 1 - .../auxio/playback/CompactPlaybackFragment.kt | 4 +- .../auxio/playback/PlaybackFragment.kt | 8 +-- .../oxycblt/auxio/playback/PlaybackService.kt | 58 +++++++++--------- .../auxio/playback/PlaybackViewModel.kt | 2 +- .../auxio/playback/queue/QueueFragment.kt | 6 +- .../playback/state/PlaybackStateManager.kt | 49 ++++++--------- .../oxycblt/auxio/search/SearchFragment.kt | 9 ++- .../oxycblt/auxio/search/SearchViewModel.kt | 5 ++ .../main/java/org/oxycblt/auxio/ui/Accent.kt | 4 +- .../org/oxycblt/auxio/ui/InterfaceUtils.kt | 24 +++++--- ...gmentBinderDelegate.kt => MemberBinder.kt} | 15 +++-- assets/shot_search_port.png | Bin 167154 -> 166909 bytes 16 files changed, 92 insertions(+), 106 deletions(-) rename app/src/main/java/org/oxycblt/auxio/ui/{FragmentBinderDelegate.kt => MemberBinder.kt} (84%) diff --git a/app/build.gradle b/app/build.gradle index e318c0179..78fef982f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 90545756d..4aa6d7582 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -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() } /** diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt index 408b4a261..4f08aab93 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -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() } /** diff --git a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt index 916d0b6d7..3b78e4d19 100644 --- a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt @@ -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) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt index 6332e380d..da38593d6 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt @@ -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, diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index bd09b1212..8c116a31c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt index 09fa5780c..0ca97f50e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt @@ -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() } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 12c4adc63..f48c3cc9b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -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. */ diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index e8670b591..7c5188948 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -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()) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index a03722d05..fe3a6c2a3 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -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() {} } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index 2e59b26e4..88120c482 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -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() { diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index e38615394..22bd8c34e 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Accent.kt b/app/src/main/java/org/oxycblt/auxio/ui/Accent.kt index bcee6fd9d..f8cb5b895 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/Accent.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/Accent.kt @@ -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. diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt index ce3a845e7..98167bd98 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt @@ -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.") diff --git a/app/src/main/java/org/oxycblt/auxio/ui/FragmentBinderDelegate.kt b/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt similarity index 84% rename from app/src/main/java/org/oxycblt/auxio/ui/FragmentBinderDelegate.kt rename to app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt index 41f9ebca5..c26c189d7 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/FragmentBinderDelegate.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt @@ -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 Fragment.memberBinding( - bindingFactory: (LayoutInflater) -> T, +fun 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( +class MemberBinder( private val fragment: Fragment, private val inflate: (LayoutInflater) -> T, private val onDestroy: T.() -> Unit @@ -36,7 +35,7 @@ class FragmentBinderDelegate( init { fragment.observeOwnerThroughCreation { - lifecycle.addObserver(this@FragmentBinderDelegate) + lifecycle.addObserver(this@MemberBinder) } } diff --git a/assets/shot_search_port.png b/assets/shot_search_port.png index 837f3991ddf92074ba234e98f26716923950c252..49ad7b7bac2ceb28d05d80c192ea12ec7efdcf6e 100755 GIT binary patch delta 2661 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 DbciA1 delta 2908 zcmaJ@c{tSj8b^|3A7&K7RL3b|9I{MO5v4*nWyz8y!Wc4hrU}jOP}z-!GPZ~gosL~u z$C89$giLXeti@#PgPCmS>-0SLIrl#I-tV99^Zwr7`+dLfXW5{NET)S5dXN9504z*k zPa9P>OrQ{oc<-LKhyzmSD*-^ECGO>cu3A+2ko3TZmSCFuvNH zc68U(8v|IuJ?&bF#+10^cQx;nTW!*dbPz?ykM?f~xsava-By-n2H6+Ww7t0+Z$0 z6d{Hgs(Ex)>CVG)zdmU=TyGjjVb6UB#q zfPS#Cwr(UV9uK4q@o_guMJ{#iBO}U7OG|uJP8zi?n&#%__wKFNJD|0-n^??e&-3%g z2g?2S<$(fMj#m!8Epnz2*`=*F{QYyQtM@W`vP%YXb91fj?9K)Uqa)lSxrgB*4$>sg> zs+Jkb$BxM|bqt_Q!w(-mgl=uD{)y2ASBZ{}e$&z-fYR5eFHiR^@IvJxPD5T^KkO1$ zxHeef50W%o>pSZ2tjQ?m|Lo~`@bcx$o?c#Kc=R0c@%{TdYierrL%Apz47Lv=$+UQy zpQ|o*_u)gCmDwQ%)9Pe=>$pSB{y74Tx*V?DsHvqzsi{HR*xIIMWlcU!Fd8eY{Nzp` zB!B$qlo>K>(9_cs6&rgM4Pn(8uP2w@_4T!`%{6r0--Gz^=+WG=vc#MmRwqqxA>>cp}PkH^zjAYU(jl#$T^a=F~`gk$zQBi9FqePj>_TN|6#-P(2&uuf$;DI``!Q+=>q;qS6OPO4Vc%c6o6%`_OY0f4 z4Q6H77^y^}H{RU^*2Nk_huS(jy;{kNauJPs!=Wb5&We7$1=&SK{)cs@>XL zyS+Me%i!jiV1Iu?|18(dHR~wxMCiU_irLHe0>8{@W2NBi@96or&1)-71=z z2~?Nh=(aZVKl1bUDk-TVk>O=!KRH*xtOMyo2f_YF^tnQhTUyKrvmecg#Ht7+FLSW-P>zsWhG%?U@$Q`IsA@5Ja$mwTF#X0sL1jW3pvP+n zINa>?Y5w^5c=iyD%H_k^TA?Uu$3=DRQAmK4|@6TQ1jDfB^Al|JbkOBDb(=B?KOJw3Y}2!hemX zAoCv*^utqp(t;JL3xu0cSJn?Ob<-_YrK0K5Gl7DoU%&do5fGxk8mBtlTgZ&1eN5)@ zZeu@I@vqmf0a}#Xx6hFhRjwYJovo5#sj5NU!`+x>2ap!hqY8`$MuoxeJbu1%#Re?B zo$H-){GudJ6^=lpW@Nl6QT?uoPBb0b1dSpnwrPf<_2eF<*TXlW@$x*u0R-{OEJ|VU3YYeH^pK(^lC(2 zci?b1BTKw<7cLw)d^q})@+-h@I)Q+=1#x)5AYZ4J7B)Tl8Vy zfB^5%PzBnMrJI`y;IAZAtDm&BajkxxP)A4L;)FS0OiYaM?%i&op&2VH1TiNdNVhH! zLF<=nZB4*8S)q%&nM~$B^7`|zF#Z}Eji27urlO}u1zOfMACr>T zMs+2b-@K^>7}Kdb=>93?C;V+nD&oGvhS z4#d$B6BBz#rM{@DqGy{#(&=<%N#X8+f%73DG$YgU_5#i!XJ@21iBLJXk=GKqnt4&Q z=^BZY3B;^^^PAu6*RRi=KabSW!LtNW1_tH;Ot!YR`v#842n$x>S<-+hh;=Trb*nCk ziHTEe_5_E;T%T`}0Q@%|R2@izhU}9~l_g*4SVTlba90Kc3;@MHvwRFN8ve&0M}V-D z49>qmX|Q0A#Q-z?HHK_HBcdmX_Uz1gk#*B^v*Nq+0) z5geR0SHDK?I=Pk6*=ZR86k@)lzpb0g< zwh@;AC1|5iB{B+bZf*xvRmT^I00oU@>MgCUys=o#-wO+yCzn`NJ<}cSj|*hvf&6bG z1UFO|g`ZNtOiWz5a>dNdOlW_?Wb$yly8}GW=OSp~wcU$|aEBb=I2?v~uP zaRujyQBmrVSQP{?mH*p4nnnG?KK|2z{&)LWaBqvx=+q9Ut2)LAK5)!UEzcAgUkm>W De+{0M