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 837f3991d..49ad7b7ba 100755 Binary files a/assets/shot_search_port.png and b/assets/shot_search_port.png differ