diff --git a/app/build.gradle b/app/build.gradle index abdfff30e..52d377573 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -118,7 +118,7 @@ dependencies { spotless { kotlin { target "src/**/*.kt" - ktlint() + ktfmt().dropboxStyle() licenseHeaderFile("NOTICE") } } diff --git a/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt b/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt index c1fb348e8..732be4436 100644 --- a/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt +++ b/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt @@ -50,11 +50,8 @@ class AuxioApp : Application(), ImageLoaderFactory { .setIcon(IconCompat.createWithResource(this, R.drawable.ic_shortcut_shuffle_24)) .setIntent( Intent(this, MainActivity::class.java) - .setAction(INTENT_KEY_SHORTCUT_SHUFFLE) - ) - .build() - ) - ) + .setAction(INTENT_KEY_SHORTCUT_SHUFFLE)) + .build())) } override fun newImageLoader() = diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index 1c2a45fc2..4afdb7b43 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -22,7 +22,6 @@ import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate -import androidx.core.app.ActivityCompat import androidx.core.view.WindowCompat import androidx.core.view.updatePadding import org.oxycblt.auxio.databinding.ActivityMainBinding diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 622b54953..7d750a43e 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -31,6 +31,8 @@ import androidx.navigation.fragment.findNavController import com.google.android.material.bottomsheet.NeoBottomSheetBehavior import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.transition.MaterialFadeThrough +import kotlin.math.max +import kotlin.math.min import org.oxycblt.auxio.databinding.FragmentMainBinding import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song @@ -49,8 +51,6 @@ import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getDimen import org.oxycblt.auxio.util.systemBarInsetsCompat import org.oxycblt.auxio.util.unlikelyToBeNull -import kotlin.math.max -import kotlin.math.min /** * A wrapper around the home fragment that shows the playback fragment and controls the more @@ -89,13 +89,9 @@ class MainFragment : // Send meaningful accessibility events for bottom sheets ViewCompat.setAccessibilityPaneTitle( - binding.playbackSheet, - context.getString(R.string.lbl_playback) - ) + binding.playbackSheet, context.getString(R.string.lbl_playback)) ViewCompat.setAccessibilityPaneTitle( - binding.queueSheet, - context.getString(R.string.lbl_queue) - ) + binding.queueSheet, context.getString(R.string.lbl_queue)) val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior? if (queueSheetBehavior != null) { @@ -104,8 +100,7 @@ class MainFragment : unlikelyToBeNull(binding.handleWrapper).setOnClickListener { if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED && - queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED - ) { + queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED) { queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_EXPANDED } } @@ -232,8 +227,7 @@ class MainFragment : when (action) { is MainNavigationAction.Expand -> tryExpandAll() is MainNavigationAction.Collapse -> tryCollapseAll() - is MainNavigationAction.Directions -> - findNavController().navigate(action.directions) + is MainNavigationAction.Directions -> findNavController().navigate(action.directions) } navModel.finishMainNavigation() @@ -328,16 +322,14 @@ class MainFragment : if (queueSheetBehavior != null && queueSheetBehavior.state != NeoBottomSheetBehavior.STATE_COLLAPSED && - playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED - ) { + playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED) { // Collapse the queue first if it is expanded. queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED return } if (playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_COLLAPSED && - playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_HIDDEN - ) { + playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_HIDDEN) { // Then collapse the playback sheet. playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED return @@ -357,9 +349,9 @@ class MainFragment : isEnabled = playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED || - queueSheetBehavior?.state == NeoBottomSheetBehavior.STATE_EXPANDED || - exploreNavController.currentDestination?.id != - exploreNavController.graph.startDestinationId + queueSheetBehavior?.state == NeoBottomSheetBehavior.STATE_EXPANDED || + exploreNavController.currentDestination?.id != + exploreNavController.graph.startDestinationId } } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 66b79e929..291607ab8 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -29,7 +29,6 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearSmoothScroller import com.google.android.material.transition.MaterialSharedAxis -import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.detail.recycler.AlbumDetailAdapter @@ -42,7 +41,6 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.picker.PickerMode import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.fragment.MusicFragment import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.canScroll @@ -93,11 +91,7 @@ class AlbumDetailFragment : collectImmediately(detailModel.currentAlbum, ::handleItemChange) collectImmediately(detailModel.albumData, detailAdapter::submitList) collectImmediately( - playbackModel.song, - playbackModel.parent, - playbackModel.isPlaying, - ::updatePlayback - ) + playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collect(navModel.exploreNavigationItem, ::handleNavigation) } @@ -130,7 +124,8 @@ class AlbumDetailFragment : override fun onItemClick(item: Item) { check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" } when (settings.detailPlaybackMode) { - null, MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) + null, + MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) MusicMode.SONGS -> playbackModel.playFromAll(item) MusicMode.ARTISTS -> doArtistDependentAction(item, PickerMode.PLAY) else -> error("Unexpected playback mode: ${settings.detailPlaybackMode}") @@ -233,8 +228,7 @@ class AlbumDetailFragment : binding.detailRecycler.post { // Make sure to increment the position to make up for the detail header binding.detailRecycler.layoutManager?.startSmoothScroll( - CenterSmoothScroller(requireContext(), pos) - ) + CenterSmoothScroller(requireContext(), pos)) // If the recyclerview can scroll, its certain that it will have to scroll to // correctly center the playing item, so make sure that the Toolbar is lifted in diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 381ce5395..9ae99370c 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -26,7 +26,6 @@ import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.google.android.material.transition.MaterialSharedAxis -import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.detail.recycler.ArtistDetailAdapter @@ -40,7 +39,6 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.picker.PickerMode import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.fragment.MusicFragment import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.collect @@ -55,7 +53,9 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * @author OxygenCobalt */ class ArtistDetailFragment : - MusicFragment(), Toolbar.OnMenuItemClickListener, DetailAdapter.Listener { + MusicFragment(), + Toolbar.OnMenuItemClickListener, + DetailAdapter.Listener { private val detailModel: DetailViewModel by activityViewModels() private val args: ArtistDetailFragmentArgs by navArgs() @@ -88,11 +88,7 @@ class ArtistDetailFragment : collectImmediately(detailModel.currentArtist, ::handleItemChange) collectImmediately(detailModel.artistData, detailAdapter::submitList) collectImmediately( - playbackModel.song, - playbackModel.parent, - playbackModel.isPlaying, - ::updatePlayback - ) + playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collect(navModel.exploreNavigationItem, ::handleNavigation) } @@ -122,7 +118,9 @@ class ArtistDetailFragment : when (item) { is Song -> { when (settings.detailPlaybackMode) { - null -> playbackModel.playFromArtist(item, unlikelyToBeNull(detailModel.currentArtist.value)) + null -> + playbackModel.playFromArtist( + item, unlikelyToBeNull(detailModel.currentArtist.value)) MusicMode.SONGS -> playbackModel.playFromAll(item) MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) MusicMode.ARTISTS -> doArtistDependentAction(item, PickerMode.PLAY) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt index e3dfe6209..de6ebe883 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt @@ -29,10 +29,10 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.appbar.AppBarLayout +import java.lang.reflect.Field import org.oxycblt.auxio.R import org.oxycblt.auxio.ui.AuxioAppBarLayout import org.oxycblt.auxio.util.lazyReflectedField -import java.lang.reflect.Field /** * An [AuxioAppBarLayout] variant that also shows the name of the toolbar whenever the detail @@ -145,6 +145,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr private const val TOOLBAR_FADE_DURATION = 150L private val TOOLBAR_TITLE_TEXT_FIELD: Field by - lazyReflectedField(Toolbar::class, "mTitleTextView") + lazyReflectedField(Toolbar::class, "mTitleTextView") } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index b61ba508b..8ddb5b77e 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -160,11 +160,12 @@ class DetailViewModel(application: Application) : private fun generateDetailSong(song: Song) { currentSongJob?.cancel() _currentSong.value = DetailSong(song, null) - currentSongJob = viewModelScope.launch(Dispatchers.IO) { - val info = generateDetailSongInfo(song) - yield() - _currentSong.value = DetailSong(song, info) - } + currentSongJob = + viewModelScope.launch(Dispatchers.IO) { + val info = generateDetailSongInfo(song) + yield() + _currentSong.value = DetailSong(song, info) + } } private fun generateDetailSongInfo(song: Song): SongInfo { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 9c6af117e..5eaadc61b 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -26,7 +26,6 @@ import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.google.android.material.transition.MaterialSharedAxis -import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.detail.recycler.DetailAdapter @@ -41,7 +40,6 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.picker.PickerMode import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.fragment.MusicFragment import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.collect @@ -56,7 +54,9 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * @author OxygenCobalt */ class GenreDetailFragment : - MusicFragment(), Toolbar.OnMenuItemClickListener, DetailAdapter.Listener { + MusicFragment(), + Toolbar.OnMenuItemClickListener, + DetailAdapter.Listener { private val detailModel: DetailViewModel by activityViewModels() private val args: GenreDetailFragmentArgs by navArgs() @@ -89,11 +89,7 @@ class GenreDetailFragment : collectImmediately(detailModel.currentGenre, ::handleItemChange) collectImmediately(detailModel.genreData, detailAdapter::submitList) collectImmediately( - playbackModel.song, - playbackModel.parent, - playbackModel.isPlaying, - ::updatePlayback - ) + playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collect(navModel.exploreNavigationItem, ::handleNavigation) } @@ -122,15 +118,16 @@ class GenreDetailFragment : override fun onItemClick(item: Item) { when (item) { is Artist -> navModel.exploreNavigateTo(item) - - is Song -> when (settings.detailPlaybackMode) { - null -> playbackModel.playFromGenre(item, unlikelyToBeNull(detailModel.currentGenre.value)) - MusicMode.SONGS -> playbackModel.playFromAll(item) - MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) - MusicMode.ARTISTS -> doArtistDependentAction(item, PickerMode.PLAY) - else -> error("Unexpected playback mode: ${settings.detailPlaybackMode}") - } - + is Song -> + when (settings.detailPlaybackMode) { + null -> + playbackModel.playFromGenre( + item, unlikelyToBeNull(detailModel.currentGenre.value)) + MusicMode.SONGS -> playbackModel.playFromAll(item) + MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) + MusicMode.ARTISTS -> doArtistDependentAction(item, PickerMode.PLAY) + else -> error("Unexpected playback mode: ${settings.detailPlaybackMode}") + } else -> error("Unexpected datatype: ${item::class.simpleName}") } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt index c1db57ab2..2396d650b 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt @@ -74,16 +74,14 @@ class SongDetailDialog : ViewBindingDialogFragment() { if (song.info.bitrateKbps != null) { binding.detailBitrate.setText( - getString(R.string.fmt_bitrate, song.info.bitrateKbps) - ) + getString(R.string.fmt_bitrate, song.info.bitrateKbps)) } else { binding.detailBitrate.setText(R.string.def_bitrate) } if (song.info.sampleRate != null) { binding.detailSampleRate.setText( - getString(R.string.fmt_sample_rate, song.info.sampleRate) - ) + getString(R.string.fmt_sample_rate, song.info.sampleRate)) } else { binding.detailSampleRate.setText(R.string.def_sample_rate) } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt index 0b5b4180c..467079c28 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt @@ -119,9 +119,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite } binding.detailInfo.apply { - val date = - item.date?.resolveDate(context) - ?: context.getString(R.string.def_date) + val date = item.date?.resolveDate(context) ?: context.getString(R.string.def_date) val songCount = context.getPlural(R.plurals.fmt_song_count, item.songs.size) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt index a0857fe76..f39464a3d 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt @@ -118,8 +118,7 @@ private class ArtistDetailViewHolder private constructor(private val binding: It binding.context.getString( R.string.fmt_two, binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size), - binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size) - ) + binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) binding.detailPlayButton.isVisible = true binding.detailShuffleButton.isVisible = true @@ -143,13 +142,14 @@ private class ArtistDetailViewHolder private constructor(private val binding: It fun new(parent: View) = ArtistDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater)) - val DIFFER = object : SimpleItemCallback() { - override fun areContentsTheSame(oldItem: Artist, newItem: Artist) = - oldItem.rawName == newItem.rawName && - oldItem.areGenreContentsTheSame(newItem) && - oldItem.albums.size == newItem.albums.size && - oldItem.songs.size == newItem.songs.size - } + val DIFFER = + object : SimpleItemCallback() { + override fun areContentsTheSame(oldItem: Artist, newItem: Artist) = + oldItem.rawName == newItem.rawName && + oldItem.areGenreContentsTheSame(newItem) && + oldItem.albums.size == newItem.albums.size && + oldItem.songs.size == newItem.songs.size + } } } @@ -159,8 +159,7 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite binding.parentImage.bind(item) binding.parentName.text = item.resolveName(binding.context) binding.parentInfo.text = - item.date?.resolveDate(binding.context) - ?: binding.context.getString(R.string.def_date) + item.date?.resolveDate(binding.context) ?: binding.context.getString(R.string.def_date) // binding.parentMenu.setOnClickListener { listener.onOpenMenu(item, it) } binding.root.setOnLongClickListener { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt index 780261460..70e33b0d6 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/DetailAdapter.kt @@ -35,7 +35,6 @@ import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.inflater -import org.oxycblt.auxio.util.logD abstract class DetailAdapter( private val listener: L, @@ -43,8 +42,7 @@ abstract class DetailAdapter( ) : IndicatorAdapter(), AuxioRecyclerView.SpanSizeLookup { private var isPlaying = false - @Suppress("LeakingThis") - override fun getItemCount() = differ.currentList.size + @Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size override fun getItemViewType(position: Int) = when (differ.currentList[position]) { @@ -85,8 +83,7 @@ abstract class DetailAdapter( return item is Header || item is SortHeader } - @Suppress("LeakingThis") - protected val differ = AsyncListDiffer(this, diffCallback) + @Suppress("LeakingThis") protected val differ = AsyncListDiffer(this, diffCallback) override val currentList: List get() = differ.currentList diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt index 41128e168..fadc2604a 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt @@ -27,9 +27,7 @@ import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.ui.recycler.ArtistViewHolder -import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.ui.recycler.SongViewHolder @@ -105,11 +103,11 @@ private class GenreDetailViewHolder private constructor(private val binding: Ite binding.detailType.text = binding.context.getString(R.string.lbl_genre) binding.detailName.text = item.resolveName(binding.context) binding.detailSubhead.isVisible = false - binding.detailInfo.text = binding.context.getString( + binding.detailInfo.text = + binding.context.getString( R.string.fmt_two, binding.context.getPlural(R.plurals.fmt_artist_count, item.artists.size), - binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size) - ) + binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) binding.detailPlayButton.setOnClickListener { listener.onPlayParent() } binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index d46d9db4c..688adb4b1 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -36,6 +36,8 @@ import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.appbar.AppBarLayout import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.transition.MaterialSharedAxis +import java.lang.reflect.Field +import kotlin.math.abs import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R @@ -63,8 +65,6 @@ import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.getColorCompat import org.oxycblt.auxio.util.lazyReflectedField import org.oxycblt.auxio.util.logD -import java.lang.reflect.Field -import kotlin.math.abs /** * The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each @@ -112,8 +112,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI binding.homeToolbar.alpha = 1f - (abs(offset.toFloat()) / (range.toFloat() / 2)) binding.homeContent.updatePadding( - bottom = binding.homeAppbar.totalScrollRange + offset - ) + bottom = binding.homeAppbar.totalScrollRange + offset) } } @@ -140,8 +139,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) = homeModel.updateCurrentTab(position) - } - ) + }) TabLayoutMediator(binding.homeTabs, this, AdaptiveTabStrategy(context, homeModel)) .attach() @@ -186,11 +184,13 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI } R.id.action_settings -> { logD("Navigating to settings") - navModel.mainNavigateTo(MainNavigationAction.Directions(MainFragmentDirections.actionShowSettings())) + navModel.mainNavigateTo( + MainNavigationAction.Directions(MainFragmentDirections.actionShowSettings())) } R.id.action_about -> { logD("Navigating to about") - navModel.mainNavigateTo(MainNavigationAction.Directions(MainFragmentDirections.actionShowAbout())) + navModel.mainNavigateTo( + MainNavigationAction.Directions(MainFragmentDirections.actionShowAbout())) } R.id.submenu_sorting -> { // Junk click event when opening the menu @@ -200,8 +200,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI homeModel.updateCurrentSort( homeModel .getSortForTab(homeModel.currentTab.value) - .withAscending(item.isChecked) - ) + .withAscending(item.isChecked)) } else -> { // Sorting option was selected, mark it as selected and update the mode @@ -209,8 +208,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI homeModel.updateCurrentSort( homeModel .getSortForTab(homeModel.currentTab.value) - .withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId))) - ) + .withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId)))) } } @@ -289,7 +287,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI binding.homeTabs.isVisible = true toolbarParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or - AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS + AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS } } @@ -445,9 +443,9 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI companion object { private val VIEW_PAGER_RECYCLER_FIELD: Field by - lazyReflectedField(ViewPager2::class, "mRecyclerView") + lazyReflectedField(ViewPager2::class, "mRecyclerView") private val VIEW_PAGER_TOUCH_SLOP_FIELD: Field by - lazyReflectedField(RecyclerView::class, "mTouchSlop") + lazyReflectedField(RecyclerView::class, "mTouchSlop") private const val KEY_LAST_TRANSITION_AXIS = BuildConfig.APPLICATION_ID + ".key.LAST_TRANSITION_AXIS" } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 82cc24c75..3079570bb 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -142,13 +142,13 @@ class HomeViewModel(application: Application) : _songs.value = settings.libSongSort.songs(library.songs) _albums.value = settings.libAlbumSort.albums(library.albums) - _artists.value = settings.libArtistSort.artists( - if (settings.shouldHideCollaborators) { - library.artists.filter { !it.isCollaborator } - } else { - library.artists - } - ) + _artists.value = + settings.libArtistSort.artists( + if (settings.shouldHideCollaborators) { + library.artists.filter { !it.isCollaborator } + } else { + library.artists + }) _genres.value = settings.libGenreSort.genres(library.genres) } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt index 5f3c59c48..328b926e5 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt @@ -21,6 +21,7 @@ import android.os.Bundle import android.text.format.DateUtils import android.view.View import android.view.ViewGroup +import java.util.Formatter import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.music.Album @@ -35,7 +36,6 @@ import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.util.collectImmediately -import java.util.Formatter /** * A [HomeListFragment] for showing a list of [Album]s. @@ -67,7 +67,8 @@ class AlbumListFragment : HomeListFragment() { is Sort.Mode.ByName -> album.collationKey?.run { sourceString.first().uppercase() } // By Artist -> Use name of first artist - is Sort.Mode.ByArtist -> album.artists[0].collationKey?.run { sourceString.first().uppercase() } + is Sort.Mode.ByArtist -> + album.artists[0].collationKey?.run { sourceString.first().uppercase() } // Year -> Use Full Year is Sort.Mode.ByDate -> album.date?.resolveDate(requireContext()) @@ -83,12 +84,11 @@ class AlbumListFragment : HomeListFragment() { val dateAddedMillis = album.dateAdded.secsToMs() formatterSb.setLength(0) DateUtils.formatDateRange( - context, - formatter, - dateAddedMillis, - dateAddedMillis, - DateUtils.FORMAT_ABBREV_ALL - ) + context, + formatter, + dateAddedMillis, + dateAddedMillis, + DateUtils.FORMAT_ABBREV_ALL) .toString() } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index 8a72da962..0695f8448 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -21,7 +21,7 @@ import android.os.Bundle import android.text.format.DateUtils import android.view.View import android.view.ViewGroup -import org.oxycblt.auxio.MainFragmentDirections +import java.util.Formatter import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.music.MusicMode @@ -32,7 +32,6 @@ import org.oxycblt.auxio.music.picker.PickerMode import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.secsToMs import org.oxycblt.auxio.settings.Settings -import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.recycler.IndicatorAdapter import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.MenuItemListener @@ -40,7 +39,6 @@ import org.oxycblt.auxio.ui.recycler.SongViewHolder import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.context -import java.util.Formatter /** * A [HomeListFragment] for showing a list of [Song]s. @@ -62,11 +60,7 @@ class SongListFragment : HomeListFragment() { collectImmediately(homeModel.songs, homeAdapter::replaceList) collectImmediately( - playbackModel.song, - playbackModel.parent, - playbackModel.isPlaying, - ::handlePlayback - ) + playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::handlePlayback) } override fun getPopup(pos: Int): String? { @@ -80,10 +74,12 @@ class SongListFragment : HomeListFragment() { is Sort.Mode.ByName -> song.collationKey?.run { sourceString.first().uppercase() } // Artist -> Use name of first artist - is Sort.Mode.ByArtist -> song.album.artists[0].collationKey?.run { sourceString.first().uppercase() } + is Sort.Mode.ByArtist -> + song.album.artists[0].collationKey?.run { sourceString.first().uppercase() } // Album -> Use Album Name - is Sort.Mode.ByAlbum -> song.album.collationKey?.run { sourceString.first().uppercase() } + is Sort.Mode.ByAlbum -> + song.album.collationKey?.run { sourceString.first().uppercase() } // Year -> Use Full Year is Sort.Mode.ByDate -> song.album.date?.resolveDate(requireContext()) @@ -96,12 +92,11 @@ class SongListFragment : HomeListFragment() { val dateAddedMillis = song.dateAdded.secsToMs() formatterSb.setLength(0) DateUtils.formatDateRange( - context, - formatter, - dateAddedMillis, - dateAddedMillis, - DateUtils.FORMAT_ABBREV_ALL - ) + context, + formatter, + dateAddedMillis, + dateAddedMillis, + DateUtils.FORMAT_ABBREV_ALL) .toString() } diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt index e0726e3ff..63f0989cd 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt @@ -62,12 +62,7 @@ sealed class Tab(open val mode: MusicMode) { * Maps between the integer code in the tab sequence and the actual [MusicMode] instance. */ private val MODE_TABLE = - arrayOf( - MusicMode.SONGS, - MusicMode.ALBUMS, - MusicMode.ARTISTS, - MusicMode.GENRES - ) + arrayOf(MusicMode.SONGS, MusicMode.ALBUMS, MusicMode.ARTISTS, MusicMode.GENRES) /** Convert an array [tabs] into a sequence of tabs. */ fun toSequence(tabs: Array): Int { diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt index 89e0cf9ed..1a775186c 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt @@ -82,8 +82,7 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) : MusicMode.ALBUMS -> R.string.lbl_albums MusicMode.ARTISTS -> R.string.lbl_artists MusicMode.GENRES -> R.string.lbl_genres - } - ) + }) isChecked = item is Tab.Visible } diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt index 6ec8780c9..b02b2cc6a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt @@ -88,8 +88,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment(), TabAd when (tab) { is Tab.Visible -> Tab.Invisible(tab.mode) is Tab.Invisible -> Tab.Visible(tab.mode) - } - ) + }) } (requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = diff --git a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt index 93faa7fcd..c2e2593fa 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt @@ -51,9 +51,7 @@ class BitmapProvider(private val context: Context) { */ @Synchronized fun load(song: Song, target: Target) { - val handle = synchronized(handleLock) { - ++currentHandle - } + val handle = synchronized(handleLock) { ++currentHandle } currentRequest?.run { disposable.dispose() } currentRequest = null @@ -77,10 +75,8 @@ class BitmapProvider(private val context: Context) { target.onCompleted(null) } } - } - ) - .transformations(SquareFrameTransform.INSTANCE) - ) + }) + .transformations(SquareFrameTransform.INSTANCE)) currentRequest = Request(context.imageLoader.enqueue(request.build()), target) } diff --git a/app/src/main/java/org/oxycblt/auxio/image/CoverMode.kt b/app/src/main/java/org/oxycblt/auxio/image/CoverMode.kt index f8ffc0cf1..246067104 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/CoverMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/CoverMode.kt @@ -28,18 +28,21 @@ enum class CoverMode { MEDIA_STORE, QUALITY; - val intCode: Int get() = when (this) { - OFF -> IntegerTable.COVER_MODE_OFF - MEDIA_STORE -> IntegerTable.COVER_MODE_MEDIA_STORE - QUALITY -> IntegerTable.COVER_MODE_QUALITY - } + val intCode: Int + get() = + when (this) { + OFF -> IntegerTable.COVER_MODE_OFF + MEDIA_STORE -> IntegerTable.COVER_MODE_MEDIA_STORE + QUALITY -> IntegerTable.COVER_MODE_QUALITY + } companion object { - fun fromIntCode(intCode: Int) = when (intCode) { - IntegerTable.COVER_MODE_OFF -> OFF - IntegerTable.COVER_MODE_MEDIA_STORE -> MEDIA_STORE - IntegerTable.COVER_MODE_QUALITY -> QUALITY - else -> null - } + fun fromIntCode(intCode: Int) = + when (intCode) { + IntegerTable.COVER_MODE_OFF -> OFF + IntegerTable.COVER_MODE_MEDIA_STORE -> MEDIA_STORE + IntegerTable.COVER_MODE_QUALITY -> QUALITY + else -> null + } } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/IndicatorView.kt b/app/src/main/java/org/oxycblt/auxio/image/IndicatorView.kt index e86a30ad1..ab307fe42 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/IndicatorView.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/IndicatorView.kt @@ -26,11 +26,11 @@ import androidx.annotation.AttrRes import androidx.appcompat.widget.AppCompatImageView import androidx.core.widget.ImageViewCompat import com.google.android.material.shape.MaterialShapeDrawable +import kotlin.math.max import org.oxycblt.auxio.R import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.getColorCompat import org.oxycblt.auxio.util.getDrawableCompat -import kotlin.math.max /** * View that displays the playback indicator. Nominally emulates [StyledImageView], but is much @@ -111,21 +111,15 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr 0f, 0f, drawable.intrinsicWidth.toFloat(), - drawable.intrinsicHeight.toFloat() - ) + drawable.intrinsicHeight.toFloat()) indicatorMatrixDst.set(0f, 0f, iconSize.toFloat(), iconSize.toFloat()) indicatorMatrix.setRectToRect( - indicatorMatrixSrc, - indicatorMatrixDst, - Matrix.ScaleToFit.CENTER - ) + indicatorMatrixSrc, indicatorMatrixDst, Matrix.ScaleToFit.CENTER) // Then actually center it into the icon, which the previous call does not // actually do. indicatorMatrix.postTranslate( - (measuredWidth - iconSize) / 2f, - (measuredHeight - iconSize) / 2f - ) + (measuredWidth - iconSize) / 2f, (measuredHeight - iconSize) / 2f) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt index 90fab0acd..44cfc81e6 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt @@ -95,9 +95,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView) val staticIcon = styledAttrs.getResourceId( - R.styleable.StyledImageView_staticIcon, - ResourcesCompat.ID_NULL - ) + R.styleable.StyledImageView_staticIcon, ResourcesCompat.ID_NULL) if (staticIcon != ResourcesCompat.ID_NULL) { this.staticIcon = context.getDrawableCompat(staticIcon) } @@ -146,8 +144,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr adjustWidth, adjustHeight, bounds.width() - adjustWidth, - bounds.height() - adjustHeight - ) + bounds.height() - adjustHeight) src.draw(canvas) } diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/BaseFetcher.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/BaseFetcher.kt index 6a69ac978..517a53337 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/extractor/BaseFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/BaseFetcher.kt @@ -22,6 +22,7 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.media.MediaMetadataRetriever +import android.util.Size as AndroidSize import androidx.core.graphics.drawable.toDrawable import coil.decode.DataSource import coil.decode.ImageSource @@ -37,6 +38,8 @@ import com.google.android.exoplayer2.MediaMetadata import com.google.android.exoplayer2.MetadataRetriever import com.google.android.exoplayer2.metadata.flac.PictureFrame import com.google.android.exoplayer2.metadata.id3.ApicFrame +import java.io.ByteArrayInputStream +import java.io.InputStream import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okio.buffer @@ -46,9 +49,6 @@ import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW -import java.io.ByteArrayInputStream -import java.io.InputStream -import android.util.Size as AndroidSize /** * The base implementation for all image fetchers in Auxio. @@ -58,8 +58,8 @@ import android.util.Size as AndroidSize */ abstract class BaseFetcher : Fetcher { /** - * Fetch the [album] cover. This call respects user configuration and has proper - * redundancy in the case that metadata fails to load. + * Fetch the [album] cover. This call respects user configuration and has proper redundancy in + * the case that metadata fails to load. */ protected suspend fun fetchCover(context: Context, album: Album): InputStream? { val settings = Settings(context) @@ -191,8 +191,7 @@ abstract class BaseFetcher : Fetcher { return SourceResult( source = ImageSource(stream.source().buffer(), context), mimeType = null, - dataSource = DataSource.DISK - ) + dataSource = DataSource.DISK) } } @@ -221,9 +220,7 @@ abstract class BaseFetcher : Fetcher { // resolution. val bitmap = SquareFrameTransform.INSTANCE.transform( - BitmapFactory.decodeStream(stream), - mosaicFrameSize - ) + BitmapFactory.decodeStream(stream), mosaicFrameSize) canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null) @@ -241,8 +238,7 @@ abstract class BaseFetcher : Fetcher { return DrawableResult( drawable = mosaicBitmap.toDrawable(context.resources), isSampled = true, - dataSource = DataSource.DISK - ) + dataSource = DataSource.DISK) } private fun Dimension.mosaicSize(): Int { diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt index 7c1ab2f2e..1b5ddfe7f 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt @@ -27,6 +27,7 @@ import coil.fetch.SourceResult import coil.key.Keyer import coil.request.Options import coil.size.Size +import kotlin.math.min import okio.buffer import okio.source import org.oxycblt.auxio.music.Album @@ -35,7 +36,6 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Sort -import kotlin.math.min /** A basic keyer for music data. */ class MusicKeyer : Keyer { @@ -60,8 +60,7 @@ private constructor(private val context: Context, private val album: Album) : Ba SourceResult( source = ImageSource(stream.source().buffer(), context), mimeType = null, - dataSource = DataSource.DISK - ) + dataSource = DataSource.DISK) } class SongFactory : Fetcher.Factory { diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index e8cc45d04..a7b62f0a6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -21,6 +21,11 @@ package org.oxycblt.auxio.music import android.content.Context import android.os.Parcelable +import java.security.MessageDigest +import java.text.CollationKey +import java.text.Collator +import java.util.UUID +import kotlin.math.max import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.oxycblt.auxio.R @@ -36,11 +41,6 @@ import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.nonZeroOrNull import org.oxycblt.auxio.util.unlikelyToBeNull -import java.security.MessageDigest -import java.text.CollationKey -import java.text.Collator -import java.util.UUID -import kotlin.math.max // --- MUSIC MODELS --- @@ -55,14 +55,14 @@ sealed class Music : Item { abstract val rawSortName: String? /** - * A key used by the sorting system that takes into account the sort tags of this item, - * any (english) articles that prefix the names, and collation rules. + * A key used by the sorting system that takes into account the sort tags of this item, any + * (english) articles that prefix the names, and collation rules. */ abstract val collationKey: CollationKey? /** - * Resolve a name from it's raw form to a form suitable to be shown in a UI. - * Null values will be resolved into their string form with this function. + * Resolve a name from it's raw form to a form suitable to be shown in a UI. Null values will be + * resolved into their string form with this function. */ abstract fun resolveName(context: Context): String @@ -75,26 +75,27 @@ sealed class Music : Item { other is Music && javaClass == other.javaClass && uid == other.uid /** - * Workaround to allow for easy collation key generation in the initializer without - * base-class initialization issues or slow lazy initialization. + * Workaround to allow for easy collation key generation in the initializer without base-class + * initialization issues or slow lazy initialization. */ protected fun makeCollationKeyImpl(): CollationKey? { - val sortName = (rawSortName ?: rawName)?.run { - when { - length > 5 && startsWith("the ", ignoreCase = true) -> substring(4) - length > 4 && startsWith("an ", ignoreCase = true) -> substring(3) - length > 3 && startsWith("a ", ignoreCase = true) -> substring(2) - else -> this + val sortName = + (rawSortName ?: rawName)?.run { + when { + length > 5 && startsWith("the ", ignoreCase = true) -> substring(4) + length > 4 && startsWith("an ", ignoreCase = true) -> substring(3) + length > 3 && startsWith("a ", ignoreCase = true) -> substring(2) + else -> this + } } - } return COLLATOR.getCollationKey(sortName) } /** - * Called when the library has been linked and validation/construction steps dependent - * on linked items should run. It's also used to do last-step initialization of fields - * that require any parent values that would not be present during startup. + * Called when the library has been linked and validation/construction steps dependent on linked + * items should run. It's also used to do last-step initialization of fields that require any + * parent values that would not be present during startup. */ abstract fun _finalize() @@ -107,15 +108,16 @@ sealed class Music : Item { * external sources, as it can persist across app restarts and does not need to encode useless * information about the relationships between items. * - * Note: While the core of a UID is a UUID. The whole is not technically a UUID, with - * string representation in particular having multiple extensions to increase uniqueness. - * Please don't try to do anything interesting with this and just assume it's a black box - * that can only be compared, serialized, and deserialized. + * Note: While the core of a UID is a UUID. The whole is not technically a UUID, with string + * representation in particular having multiple extensions to increase uniqueness. Please don't + * try to do anything interesting with this and just assume it's a black box that can only be + * compared, serialized, and deserialized. * * @author OxygenCobalt */ @Parcelize - class UID private constructor( + class UID + private constructor( private val format: Format, private val mode: MusicMode, private val uuid: UUID @@ -130,9 +132,8 @@ sealed class Music : Item { override fun hashCode() = hashCode - override fun equals(other: Any?) = other is UID && - format == other.format && - mode == other.mode && uuid == other.uuid + override fun equals(other: Any?) = + other is UID && format == other.format && mode == other.mode && uuid == other.uuid // UID string format is roughly: // format_namespace:music_mode_int-uuid @@ -151,11 +152,12 @@ sealed class Music : Item { return null } - val format = when (split[0]) { - Format.AUXIO.namespace -> Format.AUXIO - Format.MUSICBRAINZ.namespace -> Format.MUSICBRAINZ - else -> return null - } + val format = + when (split[0]) { + Format.AUXIO.namespace -> Format.AUXIO + Format.MUSICBRAINZ.namespace -> Format.MUSICBRAINZ + else -> return null + } val ids = split[1].split('-', limit = 2) if (ids.size != 2) { @@ -168,9 +170,7 @@ sealed class Music : Item { return UID(format, mode, uuid) } - /** - * Make a UUID derived from the MD5 hash of the data digested in [updates]. - */ + /** Make a UUID derived from the MD5 hash of the data digested in [updates]. */ fun auxio(mode: MusicMode, updates: MessageDigest.() -> Unit): UID { // Auxio hashes consist of the MD5 hash of the non-subjective, consistent // tags in a music item. For easier use with MusicBrainz IDs, we transform @@ -181,18 +181,13 @@ sealed class Music : Item { return UID(Format.AUXIO, mode, uuid) } - /** - * Make a UUID derived from a MusicBrainz ID. - */ - fun musicBrainz(mode: MusicMode, uuid: UUID): UID = - UID(Format.MUSICBRAINZ, mode, uuid) + /** Make a UUID derived from a MusicBrainz ID. */ + fun musicBrainz(mode: MusicMode, uuid: UUID): UID = UID(Format.MUSICBRAINZ, mode, uuid) } } companion object { - private val COLLATOR = Collator.getInstance().apply { - strength = Collator.PRIMARY - } + private val COLLATOR = Collator.getInstance().apply { strength = Collator.PRIMARY } } } @@ -207,7 +202,10 @@ sealed class MusicParent : Music() { override fun hashCode() = 31 * uid.hashCode() + songs.hashCode() override fun equals(other: Any?) = - other is MusicParent && javaClass == other.javaClass && uid == other.uid && songs == other.songs + other is MusicParent && + javaClass == other.javaClass && + uid == other.uid && + songs == other.songs } /** @@ -215,21 +213,22 @@ sealed class MusicParent : Music() { * @author OxygenCobalt */ class Song constructor(raw: Raw, settings: Settings) : Music() { - override val uid = raw.musicBrainzId?.toUuidOrNull()?.let { UID.musicBrainz(MusicMode.SONGS, it) } - ?: UID.auxio(MusicMode.SONGS) { - // Song UIDs are based on the raw data without parsing so that they remain - // consistent across music setting changes. Parents are not held up to the - // same standard since grouping is already inherently linked to settings. - update(raw.name) - update(raw.albumName) - update(raw.date) + override val uid = + raw.musicBrainzId?.toUuidOrNull()?.let { UID.musicBrainz(MusicMode.SONGS, it) } + ?: UID.auxio(MusicMode.SONGS) { + // Song UIDs are based on the raw data without parsing so that they remain + // consistent across music setting changes. Parents are not held up to the + // same standard since grouping is already inherently linked to settings. + update(raw.name) + update(raw.albumName) + update(raw.date) - update(raw.track) - update(raw.disc) + update(raw.track) + update(raw.disc) - update(raw.artistNames) - update(raw.albumArtistNames) - } + update(raw.artistNames) + update(raw.albumArtistNames) + } override val rawName = requireNotNull(raw.name) { "Invalid raw: No title" } @@ -258,15 +257,13 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { val path = Path( name = requireNotNull(raw.fileName) { "Invalid raw: No display name" }, - parent = requireNotNull(raw.directory) { "Invalid raw: No parent directory" } - ) + parent = requireNotNull(raw.directory) { "Invalid raw: No parent directory" }) /** The mime type of the audio file. Only intended for display. */ val mimeType = MimeType( fromExtension = requireNotNull(raw.extensionMimeType) { "Invalid raw: No mime type" }, - fromFormat = raw.formatMimeType - ) + fromFormat = raw.formatMimeType) /** The size of this audio file. */ val size = requireNotNull(raw.size) { "Invalid raw: No size" } @@ -280,8 +277,8 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { private var _album: Album? = null /** - * The album of this song. Every song is guaranteed to have one and only one album, - * with a "directory" album being used if no album tag can be found. + * The album of this song. Every song is guaranteed to have one and only one album, with a + * "directory" album being used if no album tag can be found. */ val album: Album get() = unlikelyToBeNull(_album) @@ -298,34 +295,41 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { private val albumArtistSortNames = raw.albumArtistSortNames.parseMultiValue(settings) - private val rawArtists = artistNames.mapIndexed { i, name -> - Artist.Raw(artistMusicBrainzIds.getOrNull(i)?.toUuidOrNull(), name, artistSortNames.getOrNull(i)) - } + private val rawArtists = + artistNames.mapIndexed { i, name -> + Artist.Raw( + artistMusicBrainzIds.getOrNull(i)?.toUuidOrNull(), + name, + artistSortNames.getOrNull(i)) + } - private val rawAlbumArtists = albumArtistNames.mapIndexed { i, name -> - Artist.Raw(albumArtistMusicBrainzIds.getOrNull(i)?.toUuidOrNull(), name, albumArtistSortNames.getOrNull(i)) - } + private val rawAlbumArtists = + albumArtistNames.mapIndexed { i, name -> + Artist.Raw( + albumArtistMusicBrainzIds.getOrNull(i)?.toUuidOrNull(), + name, + albumArtistSortNames.getOrNull(i)) + } private val _artists = mutableListOf() /** - * The artists of this song. Most often one, but there could be multiple. These artists - * are derived from the artists tag and not the album artists tag, so they may differ from - * the artists of the album. + * The artists of this song. Most often one, but there could be multiple. These artists are + * derived from the artists tag and not the album artists tag, so they may differ from the + * artists of the album. */ val artists: List get() = _artists /** - * Resolve the artists of this song into a human-readable name. First tries to use artist - * tags, then falls back to album artist tags. + * Resolve the artists of this song into a human-readable name. First tries to use artist tags, + * then falls back to album artist tags. */ - fun resolveArtistContents(context: Context) = - artists.joinToString { it.resolveName(context) } + fun resolveArtistContents(context: Context) = artists.joinToString { it.resolveName(context) } /** - * Utility method for recyclerview diffing that checks if resolveArtistContents is the - * same without a context. + * Utility method for recyclerview diffing that checks if resolveArtistContents is the same + * without a context. */ fun areArtistContentsTheSame(other: Song): Boolean { for (i in 0 until max(artists.size, other.artists.size)) { @@ -348,19 +352,18 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { val genres: List get() = _genres - /** - * Resolve the genres of the song into a human-readable string. - */ + /** Resolve the genres of the song into a human-readable string. */ fun resolveGenreContents(context: Context) = genres.joinToString { it.resolveName(context) } // --- INTERNAL FIELDS --- - val _rawGenres = raw.genreNames.parseId3GenreNames(settings) - .map { Genre.Raw(it) }.ifEmpty { listOf(Genre.Raw()) } + val _rawGenres = + raw.genreNames + .parseId3GenreNames(settings) + .map { Genre.Raw(it) } + .ifEmpty { listOf(Genre.Raw()) } - val _rawArtists = rawArtists.ifEmpty { rawAlbumArtists }.ifEmpty { - listOf(Artist.Raw()) - } + val _rawArtists = rawArtists.ifEmpty { rawAlbumArtists }.ifEmpty { listOf(Artist.Raw()) } val _rawAlbum = Album.Raw( @@ -369,8 +372,8 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { name = requireNotNull(raw.albumName) { "Invalid raw: No album name" }, sortName = raw.albumSortName, releaseType = ReleaseType.parse(raw.albumReleaseTypes.parseMultiValue(settings)), - rawArtists = rawAlbumArtists.ifEmpty { rawArtists }.ifEmpty { listOf(Artist.Raw(null, null)) } - ) + rawArtists = + rawAlbumArtists.ifEmpty { rawArtists }.ifEmpty { listOf(Artist.Raw(null, null)) }) fun _link(album: Album) { _album = album @@ -388,7 +391,7 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { checkNotNull(_album) { "Malformed song: No album" } check(_artists.isNotEmpty()) { "Malformed song: No artists" } - for( i in _artists.indices ) { + for (i in _artists.indices) { // Non-destructively reorder the linked artists so that they align with // the artist ordering within the song metadata. val newIdx = _artists[i]._getOriginalPositionIn(_rawArtists) @@ -398,7 +401,7 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { } check(_genres.isNotEmpty()) { "Malformed song: No genres" } - for( i in _genres.indices ) { + for (i in _genres.indices) { // Non-destructively reorder the linked genres so that they align with // the genre ordering within the song metadata. val newIdx = _genres[i]._getOriginalPositionIn(_rawGenres) @@ -445,14 +448,15 @@ class Song constructor(raw: Raw, settings: Settings) : Music() { * @author OxygenCobalt */ class Album constructor(raw: Raw, override val songs: List) : MusicParent() { - override val uid = raw.musicBrainzId?.let { UID.musicBrainz(MusicMode.ALBUMS, it) } - ?: UID.auxio(MusicMode.ALBUMS) { - // Hash based on only names despite the presence of a date to increase stability. - // I don't know if there is any situation where an artist will have two albums with - // the exact same name, but if there is, I would love to know. - update(raw.name) - update(raw.rawArtists.map { it.name }) - } + override val uid = + raw.musicBrainzId?.let { UID.musicBrainz(MusicMode.ALBUMS, it) } + ?: UID.auxio(MusicMode.ALBUMS) { + // Hash based on only names despite the presence of a date to increase stability. + // I don't know if there is any situation where an artist will have two albums with + // the exact same name, but if there is, I would love to know. + update(raw.name) + update(raw.rawArtists.map { it.name }) + } override val rawName = raw.name @@ -481,21 +485,19 @@ class Album constructor(raw: Raw, override val songs: List) : MusicParent( val dateAdded: Long /** - * The artists of this album. Usually one, but there may be more. These are derived from - * the album artist first, so they may differ from the song artists. + * The artists of this album. Usually one, but there may be more. These are derived from the + * album artist first, so they may differ from the song artists. */ private val _artists = mutableListOf() - val artists: List get() = _artists + val artists: List + get() = _artists + + /** Resolve the artists of this album in a human-readable manner. */ + fun resolveArtistContents(context: Context) = artists.joinToString { it.resolveName(context) } /** - * Resolve the artists of this album in a human-readable manner. - */ - fun resolveArtistContents(context: Context) = - artists.joinToString { it.resolveName(context) } - - /** - * Utility for RecyclerView differs to check if resolveArtistContents is the same without - * a context. + * Utility for RecyclerView differs to check if resolveArtistContents is the same without a + * context. */ fun areArtistContentsTheSame(other: Album): Boolean { for (i in 0 until max(artists.size, other.artists.size)) { @@ -547,7 +549,7 @@ class Album constructor(raw: Raw, override val songs: List) : MusicParent( override fun _finalize() { check(songs.isNotEmpty()) { "Malformed album: Empty" } check(_artists.isNotEmpty()) { "Malformed album: No artists" } - for( i in _artists.indices ) { + for (i in _artists.indices) { // Non-destructively reorder the linked artists so that they align with // the artist ordering within the song metadata. val newIdx = _artists[i]._getOriginalPositionIn(_rawArtists) @@ -572,9 +574,9 @@ class Album constructor(raw: Raw, override val songs: List) : MusicParent( override fun equals(other: Any?): Boolean { if (other !is Raw) return false - if (musicBrainzId != null && other.musicBrainzId != null && - musicBrainzId == other.musicBrainzId - ) { + if (musicBrainzId != null && + other.musicBrainzId != null && + musicBrainzId == other.musicBrainzId) { return true } @@ -584,15 +586,14 @@ class Album constructor(raw: Raw, override val songs: List) : MusicParent( } /** - * An abstract artist. This is derived from both album artist values and artist values in - * albums and songs respectively. + * An abstract artist. This is derived from both album artist values and artist values in albums and + * songs respectively. * @author OxygenCobalt */ -class Artist -constructor(private val raw: Raw, songAlbums: List) : MusicParent() { - override val uid = raw.musicBrainzId?.let { UID.musicBrainz(MusicMode.ARTISTS, it) } ?: UID.auxio( - MusicMode.ARTISTS - ) { update(raw.name) } +class Artist constructor(private val raw: Raw, songAlbums: List) : MusicParent() { + override val uid = + raw.musicBrainzId?.let { UID.musicBrainz(MusicMode.ARTISTS, it) } + ?: UID.auxio(MusicMode.ARTISTS) { update(raw.name) } override val rawName = raw.name @@ -602,9 +603,7 @@ constructor(private val raw: Raw, songAlbums: List) : MusicParent() { override fun resolveName(context: Context) = rawName ?: context.getString(R.string.def_artist) - /** - * The songs of this artist. This might be empty. - */ + /** The songs of this artist. This might be empty. */ override val songs: List /** The total duration of songs in this artist, in millis. Null if there are no songs. */ @@ -618,14 +617,12 @@ constructor(private val raw: Raw, songAlbums: List) : MusicParent() { private lateinit var genres: List - /** - * Resolve the combined genres of this artist into a human-readable string. - */ + /** Resolve the combined genres of this artist into a human-readable string. */ fun resolveGenreContents(context: Context) = genres.joinToString { it.resolveName(context) } /** - * Utility for RecyclerView differs to check if resolveGenreContents is the same without - * a context. + * Utility for RecyclerView differs to check if resolveGenreContents is the same without a + * context. */ fun areGenreContentsTheSame(other: Artist): Boolean { for (i in 0 until max(genres.size, other.genres.size)) { @@ -639,11 +636,10 @@ constructor(private val raw: Raw, songAlbums: List) : MusicParent() { return true } - init { val distinctSongs = mutableSetOf() val distinctAlbums = mutableSetOf() - + var noAlbums = true for (music in songAlbums) { @@ -653,13 +649,11 @@ constructor(private val raw: Raw, songAlbums: List) : MusicParent() { distinctSongs.add(music) distinctAlbums.add(music.album) } - is Album -> { music._link(this) distinctAlbums.add(music) noAlbums = false } - else -> error("Unexpected input music ${music::class.simpleName}") } } @@ -677,11 +671,17 @@ constructor(private val raw: Raw, songAlbums: List) : MusicParent() { override fun _finalize() { check(songs.isNotEmpty() || albums.isNotEmpty()) { "Malformed artist: Empty" } - genres = Sort(Sort.Mode.ByName, true).genres(songs.flatMapTo(mutableSetOf()) { it.genres }) - .sortedByDescending { genre -> songs.count { it.genres.contains(genre) } } + genres = + Sort(Sort.Mode.ByName, true) + .genres(songs.flatMapTo(mutableSetOf()) { it.genres }) + .sortedByDescending { genre -> songs.count { it.genres.contains(genre) } } } - class Raw(val musicBrainzId: UUID? = null, val name: String? = null, val sortName: String? = null) { + class Raw( + val musicBrainzId: UUID? = null, + val name: String? = null, + val sortName: String? = null + ) { private val hashCode = musicBrainzId?.hashCode() ?: name?.lowercase().hashCode() override fun hashCode() = hashCode @@ -689,9 +689,9 @@ constructor(private val raw: Raw, songAlbums: List) : MusicParent() { override fun equals(other: Any?): Boolean { if (other !is Raw) return false - if (musicBrainzId != null && other.musicBrainzId != null && - musicBrainzId == other.musicBrainzId - ) { + if (musicBrainzId != null && + other.musicBrainzId != null && + musicBrainzId == other.musicBrainzId) { return true } @@ -743,8 +743,8 @@ class Genre constructor(private val raw: Raw, override val songs: List) : durationMs = totalDuration - albums = Sort(Sort.Mode.ByName, true).albums(distinctAlbums) - .sortedByDescending { album -> + albums = + Sort(Sort.Mode.ByName, true).albums(distinctAlbums).sortedByDescending { album -> album.songs.count { it.genres.contains(this) } } @@ -819,9 +819,7 @@ fun MessageDigest.update(n: Long?) { n.shr(32).toByte(), n.shr(40).toByte(), n.shl(48).toByte(), - n.shr(56).toByte() - ) - ) + n.shr(56).toByte())) } /** @@ -851,6 +849,5 @@ fun ByteArray.toUuid(): UUID { .or(get(12).toLong().and(0xFF).shl(24)) .or(get(13).toLong().and(0xFF).shl(16)) .or(get(14).toLong().and(0xFF).shl(8)) - .or(get(15).toLong().and(0xFF)) - ) + .or(get(15).toLong().and(0xFF))) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt index bb4c0aaa4..1b8cbfd3b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -99,11 +99,10 @@ class MusicStore private constructor() { } /** - * Find a music [T] by its [uid]. If the music does not exist, or if the music is - * not [T], null will be returned. + * Find a music [T] by its [uid]. If the music does not exist, or if the music is not [T], + * null will be returned. */ - @Suppress("UNCHECKED_CAST") - fun find(uid: Music.UID) = uidMap[uid] as? T + @Suppress("UNCHECKED_CAST") fun find(uid: Music.UID) = uidMap[uid] as? T /** Sanitize an old item to find the corresponding item in a new library. */ fun sanitize(song: Song) = find(song.uid) @@ -122,8 +121,8 @@ class MusicStore private constructor() { /** Find a song for a [uri]. */ fun findSongForUri(context: Context, uri: Uri) = - context.contentResolverSafe.useQuery(uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { - cursor -> + context.contentResolverSafe.useQuery( + uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor -> cursor.moveToFirst() // We are weirdly limited to DISPLAY_NAME and SIZE when trying to locate a @@ -132,9 +131,7 @@ class MusicStore private constructor() { val displayName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)) - val size = cursor.getLong( - cursor.getColumnIndexOrThrow(OpenableColumns.SIZE) - ) + val size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)) songs.find { it.path.name == displayName && it.size == size } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt index 20bf1f974..7265970b0 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt @@ -44,9 +44,7 @@ class MusicViewModel : ViewModel(), Indexer.Callback { indexer.registerCallback(this) } - /** - * Re-index the music library. - */ + /** Re-index the music library. */ fun reindex() { indexer.requestReindex(true) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/Sort.kt b/app/src/main/java/org/oxycblt/auxio/music/Sort.kt index 281751b76..e2a6a0425 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Sort.kt @@ -18,10 +18,10 @@ package org.oxycblt.auxio.music import androidx.annotation.IdRes +import kotlin.math.max import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R import org.oxycblt.auxio.music.Sort.Mode -import kotlin.math.max /** * Represents the sort modes used in Auxio. @@ -79,11 +79,11 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { albums.sortWith(mode.getAlbumComparator(isAscending)) } - fun artistsInPlace(artists: MutableList) { + private fun artistsInPlace(artists: MutableList) { artists.sortWith(mode.getArtistComparator(isAscending)) } - fun genresInPlace(genres: MutableList) { + private fun genresInPlace(genres: MutableList) { genres.sortWith(mode.getGenreComparator(isAscending)) } @@ -141,8 +141,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { compareByDynamic(ascending, BasicComparator.ALBUM) { it.album }, compareBy(NullableComparator.INT) { it.disc }, compareBy(NullableComparator.INT) { it.track }, - compareBy(BasicComparator.SONG) - ) + compareBy(BasicComparator.SONG)) } /** Sort by the artist of an item, only supported by [Album] and [Song] */ @@ -160,15 +159,13 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { compareByDescending(BasicComparator.ALBUM) { it.album }, compareBy(NullableComparator.INT) { it.disc }, compareBy(NullableComparator.INT) { it.track }, - compareBy(BasicComparator.SONG) - ) + compareBy(BasicComparator.SONG)) override fun getAlbumComparator(ascending: Boolean): Comparator = MultiComparator( compareByDynamic(ascending, ListComparator.ARTISTS) { it.artists }, compareByDescending(NullableComparator.DATE) { it.date }, - compareBy(BasicComparator.ALBUM) - ) + compareBy(BasicComparator.ALBUM)) } /** Sort by the date of an item, only supported by [Album] and [Song] */ @@ -185,14 +182,12 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { compareByDescending(BasicComparator.ALBUM) { it.album }, compareBy(NullableComparator.INT) { it.disc }, compareBy(NullableComparator.INT) { it.track }, - compareBy(BasicComparator.SONG) - ) + compareBy(BasicComparator.SONG)) override fun getAlbumComparator(ascending: Boolean): Comparator = MultiComparator( compareByDynamic(ascending, NullableComparator.DATE) { it.date }, - compareBy(BasicComparator.ALBUM) - ) + compareBy(BasicComparator.ALBUM)) } /** Sort by the duration of the item. Supports all items. */ @@ -205,27 +200,20 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { override fun getSongComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(ascending) { it.durationMs }, - compareBy(BasicComparator.SONG) - ) + compareByDynamic(ascending) { it.durationMs }, compareBy(BasicComparator.SONG)) override fun getAlbumComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(ascending) { it.durationMs }, - compareBy(BasicComparator.ALBUM) - ) + compareByDynamic(ascending) { it.durationMs }, compareBy(BasicComparator.ALBUM)) override fun getArtistComparator(ascending: Boolean): Comparator = MultiComparator( compareByDynamic(ascending, NullableComparator.LONG) { it.durationMs }, - compareBy(BasicComparator.ARTIST) - ) + compareBy(BasicComparator.ARTIST)) override fun getGenreComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(ascending) { it.durationMs }, - compareBy(BasicComparator.GENRE) - ) + compareByDynamic(ascending) { it.durationMs }, compareBy(BasicComparator.GENRE)) } /** Sort by the amount of songs. Only applicable to music parents. */ @@ -238,21 +226,16 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { override fun getAlbumComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(ascending) { it.songs.size }, - compareBy(BasicComparator.ALBUM) - ) + compareByDynamic(ascending) { it.songs.size }, compareBy(BasicComparator.ALBUM)) override fun getArtistComparator(ascending: Boolean): Comparator = MultiComparator( compareByDynamic(ascending, NullableComparator.INT) { it.songs.size }, - compareBy(BasicComparator.ARTIST) - ) + compareBy(BasicComparator.ARTIST)) override fun getGenreComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(ascending) { it.songs.size }, - compareBy(BasicComparator.GENRE) - ) + compareByDynamic(ascending) { it.songs.size }, compareBy(BasicComparator.GENRE)) } /** Sort by the disc, and then track number of an item. Only supported by [Song]. */ @@ -267,8 +250,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { MultiComparator( compareByDynamic(ascending, NullableComparator.INT) { it.disc }, compareBy(NullableComparator.INT) { it.track }, - compareBy(BasicComparator.SONG) - ) + compareBy(BasicComparator.SONG)) } /** @@ -286,8 +268,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { MultiComparator( compareBy(NullableComparator.INT) { it.disc }, compareByDynamic(ascending, NullableComparator.INT) { it.track }, - compareBy(BasicComparator.SONG) - ) + compareBy(BasicComparator.SONG)) } /** Sort by the time the item was added. Only supported by [Song] */ @@ -300,15 +281,12 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { override fun getSongComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(ascending) { it.dateAdded }, - compareBy(BasicComparator.SONG) - ) + compareByDynamic(ascending) { it.dateAdded }, compareBy(BasicComparator.SONG)) override fun getAlbumComparator(ascending: Boolean): Comparator = MultiComparator( compareByDynamic(ascending) { album -> album.songs.minOf { it.dateAdded } }, - compareBy(BasicComparator.ALBUM) - ) + compareBy(BasicComparator.ALBUM)) } protected inline fun compareByDynamic( diff --git a/app/src/main/java/org/oxycblt/auxio/music/Tags.kt b/app/src/main/java/org/oxycblt/auxio/music/Tags.kt index 3b95afadf..00cdbfa5a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Tags.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Tags.kt @@ -19,22 +19,18 @@ package org.oxycblt.auxio.music import android.content.Context import android.os.Build -import android.text.format.DateUtils import androidx.annotation.RequiresApi -import org.oxycblt.auxio.BuildConfig -import org.oxycblt.auxio.R -import org.oxycblt.auxio.playback.secsToMs -import org.oxycblt.auxio.util.inRangeOrNull -import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.logE -import org.oxycblt.auxio.util.nonZeroOrNull import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.temporal.TemporalQueries -import java.util.Formatter import java.util.Locale import kotlin.math.max import kotlin.math.min +import org.oxycblt.auxio.BuildConfig +import org.oxycblt.auxio.R +import org.oxycblt.auxio.util.inRangeOrNull +import org.oxycblt.auxio.util.logE +import org.oxycblt.auxio.util.nonZeroOrNull /** * An ISO-8601/RFC 3339 Date. @@ -81,9 +77,8 @@ class Date private constructor(private val tokens: List) : Comparable private val second = tokens.getOrNull(5) /** - * Resolve this date into a string. This could result in a year string formatted - * as "YYYY", or a month and year string formatted as "MMM YYYY" depending on the - * situation. + * Resolve this date into a string. This could result in a year string formatted as "YYYY", or a + * month and year string formatted as "MMM YYYY" depending on the situation. */ fun resolveDate(context: Context): String { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -101,17 +96,17 @@ class Date private constructor(private val tokens: List) : Comparable @RequiresApi(Build.VERSION_CODES.O) private fun resolveFullDate(context: Context) = - if( month != null ) { - val temporal = DateTimeFormatter.ISO_DATE.parse( - "$year-$month-${day ?: 1}", - TemporalQueries.localDate() - ) + if (month != null) { + val temporal = + DateTimeFormatter.ISO_DATE.parse( + "$year-$month-${day ?: 1}", TemporalQueries.localDate()) // When it comes to songs, we only want to show the month and year. This // cannot be done with DateUtils due to it's dynamic nature, so instead // it's done with the built-in date formatter. Since the legacy date API // is awful, we only use instant and limit it to Android 8 onwards. - temporal.atStartOfDay(ZoneId.systemDefault()) + temporal + .atStartOfDay(ZoneId.systemDefault()) .format(DateTimeFormatter.ofPattern("MMM yyyy", Locale.getDefault())) } else { resolveYear(context) @@ -161,8 +156,7 @@ class Date private constructor(private val tokens: List) : Comparable companion object { private val ISO8601_REGEX = Regex( - """^(\d{4,})([-.](\d{2})([-.](\d{2})([T ](\d{2})([:.](\d{2})([:.](\d{2})(Z)?)?)?)?)?)?$""" - ) + """^(\d{4,})([-.](\d{2})([-.](\d{2})([T ](\d{2})([:.](\d{2})([:.](\d{2})(Z)?)?)?)?)?)?$""") fun from(year: Int) = fromTokens(listOf(year)) @@ -246,11 +240,12 @@ sealed class ReleaseType { data class Compilation(override val refinement: Refinement?) : ReleaseType() { override val stringRes: Int - get() = when (refinement) { - null -> R.string.lbl_compilation - Refinement.LIVE -> R.string.lbl_compilation_live - Refinement.REMIX -> R.string.lbl_compilation_remix - } + get() = + when (refinement) { + null -> R.string.lbl_compilation + Refinement.LIVE -> R.string.lbl_compilation_live + Refinement.REMIX -> R.string.lbl_compilation_remix + } } object Soundtrack : ReleaseType() { @@ -325,14 +320,15 @@ sealed class ReleaseType { private inline fun parseSecondaryTypeImpl( type: String?, convertRefinement: (Refinement?) -> ReleaseType - ) = when { - // Parse all the types that have no children - type.equals("soundtrack", true) -> Soundtrack - type.equals("mixtape/street", true) -> Mixtape - type.equals("dj-mix", true) -> Mix - type.equals("live", true) -> convertRefinement(Refinement.LIVE) - type.equals("remix", true) -> convertRefinement(Refinement.REMIX) - else -> convertRefinement(null) - } + ) = + when { + // Parse all the types that have no children + type.equals("soundtrack", true) -> Soundtrack + type.equals("mixtape/street", true) -> Mixtape + type.equals("dj-mix", true) -> Mix + type.equals("live", true) -> convertRefinement(Refinement.LIVE) + type.equals("remix", true) -> convertRefinement(Refinement.REMIX) + else -> convertRefinement(null) + } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheExtractor.kt index 52225fa45..7a432141c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheExtractor.kt @@ -24,18 +24,18 @@ import android.database.sqlite.SQLiteOpenHelper import androidx.core.database.getIntOrNull import androidx.core.database.getStringOrNull import androidx.core.database.sqlite.transaction +import java.io.File import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.queryAll import org.oxycblt.auxio.util.requireBackgroundThread -import java.io.File /** * The extractor that caches music metadata for faster use later. The cache is only responsible for - * storing "intrinsic" data, as in information derived from the file format and not - * information from the media database or file system. The exceptions are the database ID and - * modification times for files, as these are required for the cache to function well. + * storing "intrinsic" data, as in information derived from the file format and not information from + * the media database or file system. The exceptions are the database ID and modification times for + * files, as these are required for the cache to function well. * @author OxygenCobalt */ class CacheExtractor(private val context: Context, private val noop: Boolean) { @@ -55,9 +55,7 @@ class CacheExtractor(private val context: Context, private val noop: Boolean) { } } - /** - * Write a list of newly-indexed raw songs to the database. - */ + /** Write a list of newly-indexed raw songs to the database. */ fun finalize(rawSongs: List) { cacheMap = null @@ -75,14 +73,16 @@ class CacheExtractor(private val context: Context, private val noop: Boolean) { } /** - * Maybe copy a cached raw song into this instance, assuming that it has not changed - * since it was last saved. Returns true if a song was loaded. + * Maybe copy a cached raw song into this instance, assuming that it has not changed since it + * was last saved. Returns true if a song was loaded. */ fun populateFromCache(rawSong: Song.Raw): Boolean { val map = cacheMap ?: return false val cachedRawSong = map[rawSong.mediaStoreId] - if (cachedRawSong != null && cachedRawSong.dateAdded == rawSong.dateAdded && cachedRawSong.dateModified == rawSong.dateModified) { + if (cachedRawSong != null && + cachedRawSong.dateAdded == rawSong.dateAdded && + cachedRawSong.dateModified == rawSong.dateModified) { rawSong.musicBrainzId = cachedRawSong.musicBrainzId rawSong.name = cachedRawSong.name rawSong.sortName = cachedRawSong.sortName @@ -118,33 +118,35 @@ class CacheExtractor(private val context: Context, private val noop: Boolean) { } } -private class CacheDatabase(context: Context) : SQLiteOpenHelper(context, File(context.cacheDir, DB_NAME).absolutePath, null, DB_VERSION) { +private class CacheDatabase(context: Context) : + SQLiteOpenHelper(context, File(context.cacheDir, DB_NAME).absolutePath, null, DB_VERSION) { override fun onCreate(db: SQLiteDatabase) { - val command = StringBuilder() - .append("CREATE TABLE IF NOT EXISTS $TABLE_RAW_SONGS(") - .append("${Columns.MEDIA_STORE_ID} LONG PRIMARY KEY,") - .append("${Columns.DATE_ADDED} LONG NOT NULL,") - .append("${Columns.DATE_MODIFIED} LONG NOT NULL,") - .append("${Columns.SIZE} LONG NOT NULL,") - .append("${Columns.DURATION} LONG NOT NULL,") - .append("${Columns.FORMAT_MIME_TYPE} STRING,") - .append("${Columns.MUSIC_BRAINZ_ID} STRING,") - .append("${Columns.NAME} STRING NOT NULL,") - .append("${Columns.SORT_NAME} STRING,") - .append("${Columns.TRACK} INT,") - .append("${Columns.DISC} INT,") - .append("${Columns.DATE} STRING,") - .append("${Columns.ALBUM_MUSIC_BRAINZ_ID} STRING,") - .append("${Columns.ALBUM_NAME} STRING NOT NULL,") - .append("${Columns.ALBUM_SORT_NAME} STRING,") - .append("${Columns.ALBUM_RELEASE_TYPES} STRING,") - .append("${Columns.ARTIST_MUSIC_BRAINZ_IDS} STRING,") - .append("${Columns.ARTIST_NAMES} STRING,") - .append("${Columns.ARTIST_SORT_NAMES} STRING,") - .append("${Columns.ALBUM_ARTIST_MUSIC_BRAINZ_IDS} STRING,") - .append("${Columns.ALBUM_ARTIST_NAMES} STRING,") - .append("${Columns.ALBUM_ARTIST_SORT_NAMES} STRING,") - .append("${Columns.GENRE_NAMES} STRING)") + val command = + StringBuilder() + .append("CREATE TABLE IF NOT EXISTS $TABLE_RAW_SONGS(") + .append("${Columns.MEDIA_STORE_ID} LONG PRIMARY KEY,") + .append("${Columns.DATE_ADDED} LONG NOT NULL,") + .append("${Columns.DATE_MODIFIED} LONG NOT NULL,") + .append("${Columns.SIZE} LONG NOT NULL,") + .append("${Columns.DURATION} LONG NOT NULL,") + .append("${Columns.FORMAT_MIME_TYPE} STRING,") + .append("${Columns.MUSIC_BRAINZ_ID} STRING,") + .append("${Columns.NAME} STRING NOT NULL,") + .append("${Columns.SORT_NAME} STRING,") + .append("${Columns.TRACK} INT,") + .append("${Columns.DISC} INT,") + .append("${Columns.DATE} STRING,") + .append("${Columns.ALBUM_MUSIC_BRAINZ_ID} STRING,") + .append("${Columns.ALBUM_NAME} STRING NOT NULL,") + .append("${Columns.ALBUM_SORT_NAME} STRING,") + .append("${Columns.ALBUM_RELEASE_TYPES} STRING,") + .append("${Columns.ARTIST_MUSIC_BRAINZ_IDS} STRING,") + .append("${Columns.ARTIST_NAMES} STRING,") + .append("${Columns.ARTIST_SORT_NAMES} STRING,") + .append("${Columns.ALBUM_ARTIST_MUSIC_BRAINZ_IDS} STRING,") + .append("${Columns.ALBUM_ARTIST_NAMES} STRING,") + .append("${Columns.ALBUM_ARTIST_SORT_NAMES} STRING,") + .append("${Columns.GENRE_NAMES} STRING)") db.execSQL(command.toString()) } @@ -185,18 +187,22 @@ private class CacheDatabase(context: Context) : SQLiteOpenHelper(context, File(c val discIndex = cursor.getColumnIndexOrThrow(Columns.DISC) val dateIndex = cursor.getColumnIndexOrThrow(Columns.DATE) - val albumMusicBrainzIdIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_MUSIC_BRAINZ_ID) + val albumMusicBrainzIdIndex = + cursor.getColumnIndexOrThrow(Columns.ALBUM_MUSIC_BRAINZ_ID) val albumNameIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_NAME) val albumSortNameIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_SORT_NAME) val albumReleaseTypesIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_RELEASE_TYPES) - val artistMusicBrainzIdsIndex = cursor.getColumnIndexOrThrow(Columns.ARTIST_MUSIC_BRAINZ_IDS) + val artistMusicBrainzIdsIndex = + cursor.getColumnIndexOrThrow(Columns.ARTIST_MUSIC_BRAINZ_IDS) val artistNamesIndex = cursor.getColumnIndexOrThrow(Columns.ARTIST_NAMES) val artistSortNamesIndex = cursor.getColumnIndexOrThrow(Columns.ARTIST_SORT_NAMES) - val albumArtistMusicBrainzIdsIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_ARTIST_MUSIC_BRAINZ_IDS) + val albumArtistMusicBrainzIdsIndex = + cursor.getColumnIndexOrThrow(Columns.ALBUM_ARTIST_MUSIC_BRAINZ_IDS) val albumArtistNamesIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_ARTIST_NAMES) - val albumArtistSortNamesIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_ARTIST_SORT_NAMES) + val albumArtistSortNamesIndex = + cursor.getColumnIndexOrThrow(Columns.ALBUM_ARTIST_SORT_NAMES) val genresIndex = cursor.getColumnIndexOrThrow(Columns.GENRE_NAMES) @@ -223,26 +229,31 @@ private class CacheDatabase(context: Context) : SQLiteOpenHelper(context, File(c raw.albumMusicBrainzId = cursor.getStringOrNull(albumMusicBrainzIdIndex) raw.albumName = cursor.getString(albumNameIndex) raw.albumSortName = cursor.getStringOrNull(albumSortNameIndex) - cursor.getStringOrNull(albumReleaseTypesIndex)?.parseMultiValue() - ?.let { raw.albumReleaseTypes = it } + cursor.getStringOrNull(albumReleaseTypesIndex)?.parseMultiValue()?.let { + raw.albumReleaseTypes = it + } cursor.getStringOrNull(artistMusicBrainzIdsIndex)?.let { raw.artistMusicBrainzIds = it.parseMultiValue() } - cursor.getStringOrNull(artistNamesIndex) - ?.let { raw.artistNames = it.parseMultiValue() } - cursor.getStringOrNull(artistSortNamesIndex) - ?.let { raw.artistSortNames = it.parseMultiValue() } + cursor.getStringOrNull(artistNamesIndex)?.let { + raw.artistNames = it.parseMultiValue() + } + cursor.getStringOrNull(artistSortNamesIndex)?.let { + raw.artistSortNames = it.parseMultiValue() + } - cursor.getStringOrNull(albumArtistMusicBrainzIdsIndex) - ?.let { raw.albumArtistMusicBrainzIds = it.parseMultiValue() } - cursor.getStringOrNull(albumArtistNamesIndex) - ?.let { raw.albumArtistNames = it.parseMultiValue() } - cursor.getStringOrNull(albumArtistSortNamesIndex) - ?.let { raw.albumArtistSortNames = it.parseMultiValue() } + cursor.getStringOrNull(albumArtistMusicBrainzIdsIndex)?.let { + raw.albumArtistMusicBrainzIds = it.parseMultiValue() + } + cursor.getStringOrNull(albumArtistNamesIndex)?.let { + raw.albumArtistNames = it.parseMultiValue() + } + cursor.getStringOrNull(albumArtistSortNamesIndex)?.let { + raw.albumArtistSortNames = it.parseMultiValue() + } - cursor.getStringOrNull(genresIndex) - ?.let { raw.genreNames = it.parseMultiValue() } + cursor.getStringOrNull(genresIndex)?.let { raw.genreNames = it.parseMultiValue() } map[id] = raw } @@ -287,15 +298,23 @@ private class CacheDatabase(context: Context) : SQLiteOpenHelper(context, File(c put(Columns.ALBUM_MUSIC_BRAINZ_ID, rawSong.albumMusicBrainzId) put(Columns.ALBUM_NAME, rawSong.albumName) put(Columns.ALBUM_SORT_NAME, rawSong.albumSortName) - put(Columns.ALBUM_RELEASE_TYPES, rawSong.albumReleaseTypes.toMultiValue()) + put( + Columns.ALBUM_RELEASE_TYPES, + rawSong.albumReleaseTypes.toMultiValue()) - put(Columns.ARTIST_MUSIC_BRAINZ_IDS, rawSong.artistMusicBrainzIds.toMultiValue()) + put( + Columns.ARTIST_MUSIC_BRAINZ_IDS, + rawSong.artistMusicBrainzIds.toMultiValue()) put(Columns.ARTIST_NAMES, rawSong.artistNames.toMultiValue()) put(Columns.ARTIST_SORT_NAMES, rawSong.artistSortNames.toMultiValue()) - put(Columns.ALBUM_ARTIST_MUSIC_BRAINZ_IDS, rawSong.albumArtistMusicBrainzIds.toMultiValue()) + put( + Columns.ALBUM_ARTIST_MUSIC_BRAINZ_IDS, + rawSong.albumArtistMusicBrainzIds.toMultiValue()) put(Columns.ALBUM_ARTIST_NAMES, rawSong.albumArtistNames.toMultiValue()) - put(Columns.ALBUM_ARTIST_SORT_NAMES, rawSong.albumArtistSortNames.toMultiValue()) + put( + Columns.ALBUM_ARTIST_SORT_NAMES, + rawSong.albumArtistSortNames.toMultiValue()) put(Columns.GENRE_NAMES, rawSong.genreNames.toMultiValue()) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt index 17fe17a07..908131d15 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt @@ -26,6 +26,7 @@ import android.provider.MediaStore import androidx.annotation.RequiresApi import androidx.core.database.getIntOrNull import androidx.core.database.getStringOrNull +import java.io.File import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.storage.Directory import org.oxycblt.auxio.music.storage.directoryCompat @@ -37,7 +38,6 @@ import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.contentResolverSafe import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.logD -import java.io.File /* * This file acts as the base for most the black magic required to get a remotely sensible music @@ -100,7 +100,10 @@ import java.io.File * music loading process. * @author OxygenCobalt */ -abstract class MediaStoreExtractor(private val context: Context, private val cacheDatabase: CacheExtractor) { +abstract class MediaStoreExtractor( + private val context: Context, + private val cacheDatabase: CacheExtractor +) { private var cursor: Cursor? = null private var idIndex = -1 @@ -173,13 +176,11 @@ abstract class MediaStoreExtractor(private val context: Context, private val cac val cursor = requireNotNull( - context.contentResolverSafe.queryCursor( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - projection, - selector, - args.toTypedArray() - ) - ) { "Content resolver failure: No Cursor returned" } + context.contentResolverSafe.queryCursor( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + projection, + selector, + args.toTypedArray())) { "Content resolver failure: No Cursor returned" } .also { cursor = it } idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID) @@ -207,8 +208,7 @@ abstract class MediaStoreExtractor(private val context: Context, private val cac // obscure formats where genre support is only really covered by this. context.contentResolverSafe.useQuery( MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, - arrayOf(MediaStore.Audio.Genres._ID, MediaStore.Audio.Genres.NAME) - ) { genreCursor -> + arrayOf(MediaStore.Audio.Genres._ID, MediaStore.Audio.Genres.NAME)) { genreCursor -> val idIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID) val nameIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME) @@ -218,8 +218,7 @@ abstract class MediaStoreExtractor(private val context: Context, private val cac context.contentResolverSafe.useQuery( MediaStore.Audio.Genres.Members.getContentUri(VOLUME_EXTERNAL, id), - arrayOf(MediaStore.Audio.Genres.Members._ID) - ) { cursor -> + arrayOf(MediaStore.Audio.Genres.Members._ID)) { cursor -> val songIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.Members._ID) @@ -292,15 +291,14 @@ abstract class MediaStoreExtractor(private val context: Context, private val cac MediaStore.Audio.AudioColumns.ALBUM, MediaStore.Audio.AudioColumns.ALBUM_ID, MediaStore.Audio.AudioColumns.ARTIST, - AUDIO_COLUMN_ALBUM_ARTIST - ) + AUDIO_COLUMN_ALBUM_ARTIST) protected abstract val dirSelector: String protected abstract fun addDirToSelectorArgs(dir: Directory, args: MutableList): Boolean /** - * Populate the "file data" of the cursor, or data that is required to access a cache entry - * or makes no sense to cache. This includes database IDs, modification dates, + * Populate the "file data" of the cursor, or data that is required to access a cache entry or + * makes no sense to cache. This includes database IDs, modification dates, */ protected open fun populateFileData(cursor: Cursor, raw: Song.Raw) { raw.mediaStoreId = cursor.getLong(idIndex) @@ -315,9 +313,7 @@ abstract class MediaStoreExtractor(private val context: Context, private val cac raw.albumMediaStoreId = cursor.getLong(albumIdIndex) } - /** - * Extract cursor metadata into [raw]. - */ + /** Extract cursor metadata into [raw]. */ protected open fun populateMetadata(cursor: Cursor, raw: Song.Raw) { raw.name = cursor.getString(titleIndex) @@ -362,8 +358,7 @@ abstract class MediaStoreExtractor(private val context: Context, private val cac * External has existed since at least API 21, but no constant existed for it until API 29. * This constant is safe to use. */ - @Suppress("InlinedApi") - private const val VOLUME_EXTERNAL = MediaStore.VOLUME_EXTERNAL + @Suppress("InlinedApi") private const val VOLUME_EXTERNAL = MediaStore.VOLUME_EXTERNAL /** * The base selector that works across all versions of android. Does not exclude @@ -377,8 +372,8 @@ abstract class MediaStoreExtractor(private val context: Context, private val cac // speed, we only want to add redundancy on known issues, not with possible issues. /** - * A [MediaStoreExtractor] that completes the music loading process in a way compatible from - * API 21 onwards to API 29. + * A [MediaStoreExtractor] that completes the music loading process in a way compatible from API 21 + * onwards to API 29. * @author OxygenCobalt */ class Api21MediaStoreExtractor(context: Context, cacheDatabase: CacheExtractor) : @@ -471,8 +466,7 @@ open class BaseApi29MediaStoreExtractor(context: Context, cacheDatabase: CacheEx super.projection + arrayOf( MediaStore.Audio.AudioColumns.VOLUME_NAME, - MediaStore.Audio.AudioColumns.RELATIVE_PATH - ) + MediaStore.Audio.AudioColumns.RELATIVE_PATH) override val dirSelector: String get() = @@ -502,8 +496,8 @@ open class BaseApi29MediaStoreExtractor(context: Context, cacheDatabase: CacheEx } /** - * A [MediaStoreExtractor] that completes the music loading process in a way compatible with at least - * API 29. + * A [MediaStoreExtractor] that completes the music loading process in a way compatible with at + * least API 29. * @author OxygenCobalt */ @RequiresApi(Build.VERSION_CODES.Q) @@ -534,8 +528,8 @@ open class Api29MediaStoreExtractor(context: Context, cacheDatabase: CacheExtrac } /** - * A [MediaStoreExtractor] that completes the music loading process in a way compatible with at least - * API 30. + * A [MediaStoreExtractor] that completes the music loading process in a way compatible with at + * least API 30. * @author OxygenCobalt */ @RequiresApi(Build.VERSION_CODES.R) @@ -556,8 +550,7 @@ class Api30MediaStoreExtractor(context: Context, cacheDatabase: CacheExtractor) super.projection + arrayOf( MediaStore.Audio.AudioColumns.CD_TRACK_NUMBER, - MediaStore.Audio.AudioColumns.DISC_NUMBER - ) + MediaStore.Audio.AudioColumns.DISC_NUMBER) override fun populateMetadata(cursor: Cursor, raw: Song.Raw) { super.populateMetadata(cursor, raw) diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt index 0d5e20421..021dd222f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt @@ -45,7 +45,10 @@ import org.oxycblt.auxio.util.logW * * @author OxygenCobalt */ -class MetadataExtractor(private val context: Context, private val mediaStoreExtractor: MediaStoreExtractor) { +class MetadataExtractor( + private val context: Context, + private val mediaStoreExtractor: MediaStoreExtractor +) { private val taskPool: Array = arrayOfNulls(TASK_CAPACITY) /** Initialize the sub-layers that this layer relies on. */ @@ -116,8 +119,7 @@ class Task(context: Context, private val raw: Song.Raw) { private val future = MetadataRetriever.retrieveMetadata( context, - MediaItem.fromUri(requireNotNull(raw.mediaStoreId) { "Invalid raw: No id" }.audioUri) - ) + MediaItem.fromUri(requireNotNull(raw.mediaStoreId) { "Invalid raw: No id" }.audioUri)) /** * Get the song that this task is trying to complete. If the task is still busy, this will @@ -215,20 +217,16 @@ class Task(context: Context, private val raw: Song.Raw) { // 3. ID3v2.4 Release Date, as it is the second most common date type // 4. ID3v2.3 Original Date, as it is like #1 // 5. ID3v2.3 Release Year, as it is the most common date type - ( - tags["TDOR"]?.run { get(0).parseTimestamp() } + (tags["TDOR"]?.run { get(0).parseTimestamp() } ?: tags["TDRC"]?.run { get(0).parseTimestamp() } - ?: tags["TDRL"]?.run { get(0).parseTimestamp() } ?: parseId3v23Date(tags) - ) + ?: tags["TDRL"]?.run { get(0).parseTimestamp() } ?: parseId3v23Date(tags)) ?.let { raw.date = it } // Album tags["TXXX:MusicBrainz Album Id"]?.let { raw.albumMusicBrainzId = it[0] } tags["TALB"]?.let { raw.albumName = it[0] } tags["TSOA"]?.let { raw.albumSortName = it[0] } - (tags["TXXX:MusicBrainz Album Type"] ?: tags["GRP1"])?.let { - raw.albumReleaseTypes = it - } + (tags["TXXX:MusicBrainz Album Type"] ?: tags["GRP1"])?.let { raw.albumReleaseTypes = it } // Artist tags["TXXX:MusicBrainz Artist Id"]?.let { raw.artistMusicBrainzIds = it } @@ -288,11 +286,9 @@ class Task(context: Context, private val raw: Song.Raw) { // 2. Date, as it is the most common date type // 3. Year, as old vorbis tags tended to use this (I know this because it's the only // tag that android supports, so it must be 15 years old or more!) - ( - tags["ORIGINALDATE"]?.run { get(0).parseTimestamp() } + (tags["ORIGINALDATE"]?.run { get(0).parseTimestamp() } ?: tags["DATE"]?.run { get(0).parseTimestamp() } - ?: tags["YEAR"]?.run { get(0).parseYear() } - ) + ?: tags["YEAR"]?.run { get(0).parseYear() }) ?.let { raw.date = it } // Album diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/ParsingUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/ParsingUtil.kt index 5db18f3fc..9b58f798e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/ParsingUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/ParsingUtil.kt @@ -18,10 +18,10 @@ package org.oxycblt.auxio.music.extractor import androidx.core.text.isDigitsOnly +import java.util.UUID import org.oxycblt.auxio.music.Date import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.nonZeroOrNull -import java.util.UUID /** * Parse out the track number field as if the given Int is formatted as DTTT, where D Is the disc @@ -51,9 +51,7 @@ fun String.parseYear() = toIntOrNull()?.toDate() /** Parse an ISO-8601 time-stamp from this field into a [Date]. */ fun String.parseTimestamp() = Date.from(this) -/** - * Parse a string by [selector], also handling string escaping. - */ +/** Parse a string by [selector], also handling string escaping. */ inline fun String.splitEscaped(selector: (Char) -> Boolean): MutableList { val split = mutableListOf() var currentString = "" @@ -110,11 +108,12 @@ fun String.maybeParseSeparators(settings: Settings): List { return splitEscaped { separators.contains(it) } } -fun String.toUuidOrNull(): UUID? = try { - UUID.fromString(this) -} catch (e: IllegalArgumentException) { - null -} +fun String.toUuidOrNull(): UUID? = + try { + UUID.fromString(this) + } catch (e: IllegalArgumentException) { + null + } /** * Parse a multi-value genre name using ID3v2 rules. If there is one value, the ID3v2.3 rules will @@ -392,5 +391,4 @@ private val GENRE_TABLE = "Psybient", // Auxio's extensions (Future garage is also based and deserves a slot) - "Future Garage" - ) + "Future Garage") diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt index ebe2fa62d..fdb1f611a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt @@ -27,10 +27,9 @@ import org.oxycblt.auxio.ui.recycler.ItemClickListener import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.inflater -/** - * The adapter that displays a list of artist choices in the picker UI. - */ -class ArtistChoiceAdapter(private val listener: ItemClickListener) : RecyclerView.Adapter() { +/** The adapter that displays a list of artist choices in the picker UI. */ +class ArtistChoiceAdapter(private val listener: ItemClickListener) : + RecyclerView.Adapter() { private var artists = listOf() override fun getItemCount() = artists.size @@ -45,8 +44,7 @@ class ArtistChoiceAdapter(private val listener: ItemClickListener) : RecyclerVie if (newArtists != artists) { artists = newArtists - @Suppress("NotifyDataSetChanged") - notifyDataSetChanged() + @Suppress("NotifyDataSetChanged") notifyDataSetChanged() } } } @@ -55,13 +53,12 @@ class ArtistChoiceAdapter(private val listener: ItemClickListener) : RecyclerVie * The ViewHolder that displays a artist choice. Smaller than other parent items due to dialog * constraints. */ -class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) : DialogViewHolder(binding.root) { +class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) : + DialogViewHolder(binding.root) { fun bind(artist: Artist, listener: ItemClickListener) { binding.pickerImage.bind(artist) binding.pickerName.text = artist.resolveName(binding.context) - binding.root.setOnClickListener { - listener.onItemClick(artist) - } + binding.root.setOnClickListener { listener.onItemClick(artist) } } companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt index c5997ba68..477a5ae6f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt @@ -44,7 +44,8 @@ import org.oxycblt.auxio.util.collectImmediately * * TODO: Clean up the picker flow to reduce the amount of duplication I had to do. */ -class ArtistPickerDialog : ViewBindingDialogFragment(), ItemClickListener { +class ArtistPickerDialog : + ViewBindingDialogFragment(), ItemClickListener { private val pickerModel: PickerViewModel by viewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val navModel: NavigationViewModel by activityViewModels() @@ -56,9 +57,7 @@ class ArtistPickerDialog : ViewBindingDialogFragment() DialogMusicPickerBinding.inflate(inflater) override fun onConfigDialog(builder: AlertDialog.Builder) { - builder - .setTitle(R.string.lbl_artists) - .setNegativeButton(R.string.lbl_cancel, null) + builder.setTitle(R.string.lbl_artists).setNegativeButton(R.string.lbl_cancel, null) } override fun onBindingCreated(binding: DialogMusicPickerBinding, savedInstanceState: Bundle?) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/PickerMode.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/PickerMode.kt index baf736214..b5207cd8c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/PickerMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/PickerMode.kt @@ -17,9 +17,7 @@ package org.oxycblt.auxio.music.picker -/** - * Represents the actions available to the picker UI. - */ +/** Represents the actions available to the picker UI. */ enum class PickerMode { PLAY, SHOW diff --git a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt index 0aa02cb32..93064193e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt @@ -98,8 +98,7 @@ class MusicDirsDialog : dirs = MusicDirs( pendingDirs.mapNotNull { Directory.fromDocumentUri(storageManager, it) }, - savedInstanceState.getBoolean(KEY_PENDING_MODE) - ) + savedInstanceState.getBoolean(KEY_PENDING_MODE)) } } @@ -112,8 +111,7 @@ class MusicDirsDialog : R.id.dirs_mode_include } else { R.id.dirs_mode_exclude - } - ) + }) updateMode() addOnButtonCheckedListener { _, _, _ -> updateMode() } @@ -123,9 +121,7 @@ class MusicDirsDialog : override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putStringArrayList( - KEY_PENDING_DIRS, - ArrayList(dirAdapter.dirs.map { it.toString() }) - ) + KEY_PENDING_DIRS, ArrayList(dirAdapter.dirs.map { it.toString() })) outState.putBoolean(KEY_PENDING_MODE, isInclude(requireBinding())) } @@ -159,9 +155,7 @@ class MusicDirsDialog : // Turn the raw URI into a document tree URI val docUri = DocumentsContract.buildDocumentUriUsingTree( - uri, - DocumentsContract.getTreeDocumentId(uri) - ) + uri, DocumentsContract.getTreeDocumentId(uri)) // Turn it into a semi-usable path val treeUri = DocumentsContract.getTreeDocumentId(docUri) diff --git a/app/src/main/java/org/oxycblt/auxio/music/storage/StorageUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/storage/StorageUtil.kt index d378e2d64..8626e0aac 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/storage/StorageUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/storage/StorageUtil.kt @@ -30,10 +30,10 @@ import android.os.storage.StorageVolume import android.provider.MediaStore import android.webkit.MimeTypeMap import com.google.android.exoplayer2.util.MimeTypes -import org.oxycblt.auxio.R -import org.oxycblt.auxio.util.lazyReflectedMethod import java.io.File import java.lang.reflect.Method +import org.oxycblt.auxio.R +import org.oxycblt.auxio.util.lazyReflectedMethod /** A path to a file. [name] is the stripped file name, [parent] is the parent path. */ data class Path(val name: String, val parent: Directory) @@ -70,9 +70,7 @@ class Directory private constructor(val volume: StorageVolume, val relativePath: fun from(volume: StorageVolume, relativePath: String) = Directory( - volume, - relativePath.removePrefix(File.separator).removeSuffix(File.separator) - ) + volume, relativePath.removePrefix(File.separator).removeSuffix(File.separator)) /** * Converts an opaque document uri in the form of VOLUME:PATH into a [Directory]. This is a @@ -198,15 +196,14 @@ val Long.albumCoverUri: Uri @Suppress("NewApi") private val SM_API21_GET_VOLUME_LIST_METHOD: Method by -lazyReflectedMethod(StorageManager::class, "getVolumeList") + lazyReflectedMethod(StorageManager::class, "getVolumeList") @Suppress("NewApi") private val SV_API21_GET_PATH_METHOD: Method by lazyReflectedMethod(StorageVolume::class, "getPath") /** The "primary" storage volume containing the OS. May be an SD Card. */ val StorageManager.primaryStorageVolumeCompat: StorageVolume - @Suppress("NewApi") - get() = primaryStorageVolume + @Suppress("NewApi") get() = primaryStorageVolume /** * A list of recognized volumes, retrieved in a compatible manner. Note that these volumes may be @@ -243,13 +240,11 @@ fun StorageVolume.getDescriptionCompat(context: Context): String = getDescriptio /** If this volume is the primary volume. May still be removable storage. */ val StorageVolume.isPrimaryCompat: Boolean - @SuppressLint("NewApi") - get() = isPrimary + @SuppressLint("NewApi") get() = isPrimary /** If this volume is emulated. */ val StorageVolume.isEmulatedCompat: Boolean - @SuppressLint("NewApi") - get() = isEmulated + @SuppressLint("NewApi") get() = isEmulated /** * If this volume corresponds to "Internal shared storage", represented in document URIs as @@ -260,13 +255,11 @@ val StorageVolume.isInternalCompat: Boolean /** Returns the UUID of the volume in a compatible manner. */ val StorageVolume.uuidCompat: String? - @SuppressLint("NewApi") - get() = uuid + @SuppressLint("NewApi") get() = uuid /** Returns the state of the volume in a compatible manner. */ val StorageVolume.stateCompat: String - @SuppressLint("NewApi") - get() = state + @SuppressLint("NewApi") get() = state /** * Returns the name of this volume as it is used in [MediaStore]. This will be diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt index b55a2a24e..28c6cd51d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt @@ -133,9 +133,9 @@ class Indexer { /** * Start the indexing process. This should be done by [Controller] in a background thread. When * complete, a new completion state will be pushed to each callback. - * @param fresh Whether to use the cache when loading. + * @param withCache Whether to use the cache when loading. */ - suspend fun index(context: Context, fresh: Boolean) { + suspend fun index(context: Context, withCache: Boolean) { val notGranted = ContextCompat.checkSelfPermission(context, PERMISSION_READ_AUDIO) == PackageManager.PERMISSION_DENIED @@ -148,12 +148,11 @@ class Indexer { val response = try { val start = System.currentTimeMillis() - val library = indexImpl(context, fresh) + val library = indexImpl(context, withCache) if (library != null) { logD( "Music indexing completed successfully in " + - "${System.currentTimeMillis() - start}ms" - ) + "${System.currentTimeMillis() - start}ms") Response.Ok(library) } else { logE("No music found") @@ -194,9 +193,7 @@ class Indexer { emitIndexing(null) } - /** - * Run the proper music loading process. - */ + /** Run the proper music loading process. */ private suspend fun indexImpl(context: Context, withCache: Boolean): MusicStore.Library? { // Create the chain of extractors. Each extractor builds on the previous and // enables version-specific features in order to create the best possible music @@ -250,12 +247,15 @@ class Indexer { } /** - * Does the initial query over the song database using [metadataExtractor]. The songs returned by - * this function are **not** well-formed. The companion [buildAlbums], [buildArtists], and + * Does the initial query over the song database using [metadataExtractor]. The songs returned + * by this function are **not** well-formed. The companion [buildAlbums], [buildArtists], and * [buildGenres] functions must be called with the returned list so that all songs are properly * linked up. */ - private suspend fun buildSongs(metadataExtractor: MetadataExtractor, settings: Settings): List { + private suspend fun buildSongs( + metadataExtractor: MetadataExtractor, + settings: Settings + ): List { logD("Starting indexing process") val start = System.currentTimeMillis() @@ -317,9 +317,9 @@ class Indexer { } /** - * Group up songs AND albums into artists. This process seems weird (because it is), but - * the purpose is that the actual artist information of albums and songs often differs, - * and so they are linked in different ways. + * Group up songs AND albums into artists. This process seems weird (because it is), but the + * purpose is that the actual artist information of albums and songs often differs, and so they + * are linked in different ways. */ private fun buildArtists(songs: List, albums: List): List { val musicByArtist = mutableMapOf>() diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt index 145df5c92..e38ec29a8 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt @@ -67,8 +67,7 @@ class IndexingNotification(private val context: Context) : // Only update the notification every 1.5s to prevent rate-limiting. logD("Updating state to $indexing") setContentText( - context.getString(R.string.fmt_indexing, indexing.current, indexing.total) - ) + context.getString(R.string.fmt_indexing, indexing.current, indexing.total)) setProgress(indexing.total, indexing.current, false) return true } @@ -95,6 +94,4 @@ class ObservingNotification(context: Context) : ServiceNotification(context, IND private val INDEXER_CHANNEL = ServiceNotification.ChannelInfo( - id = BuildConfig.APPLICATION_ID + ".channel.INDEXER", - nameRes = R.string.lbl_indexer - ) + id = BuildConfig.APPLICATION_ID + ".channel.INDEXER", nameRes = R.string.lbl_indexer) diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt index 3478e53f3..bfd214404 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt @@ -79,9 +79,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { wakeLock = getSystemServiceCompat(PowerManager::class) .newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - BuildConfig.APPLICATION_ID + ":IndexerService" - ) + PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":IndexerService") settings = Settings(this, this) indexerContentObserver = SystemContentObserver() @@ -130,8 +128,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { when (state) { is Indexer.State.Complete -> { if (state.response is Indexer.Response.Ok && - state.response.library != musicStore.library - ) { + state.response.library != musicStore.library) { logD("Applying new library") val newLibrary = state.response.library @@ -243,10 +240,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { ) : ContentObserver(handler), Runnable { init { contentResolverSafe.registerContentObserver( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - true, - this - ) + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, this) } fun release() { @@ -263,8 +257,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { // Check here if we should even start a reindex. This is much less bug-prone than // registering and de-registering this component as this setting changes. if (settings.shouldBeObserving) { - onSt - artIndexing(true) + onStartIndexing(true) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/ActionMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/ActionMode.kt index 6bceaacb1..2c4b54fe9 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/ActionMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/ActionMode.kt @@ -26,11 +26,12 @@ enum class ActionMode { SHUFFLE; val intCode: Int - get() = when (this) { - NEXT -> IntegerTable.ACTION_MODE_NEXT - REPEAT -> IntegerTable.ACTION_MODE_REPEAT - SHUFFLE -> IntegerTable.ACTION_MODE_SHUFFLE - } + get() = + when (this) { + NEXT -> IntegerTable.ACTION_MODE_NEXT + REPEAT -> IntegerTable.ACTION_MODE_REPEAT + SHUFFLE -> IntegerTable.ACTION_MODE_SHUFFLE + } companion object { /** Convert an int [code] into an instance, or null if it isn't valid. */ diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index 2df693198..3cd15aa56 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -89,16 +89,12 @@ class PlaybackPanelFragment : binding.playbackArtist.apply { isSelected = true - setOnClickListener { - playbackModel.song.value?.let { showCurrentArtist() } - } + setOnClickListener { playbackModel.song.value?.let { showCurrentArtist() } } } binding.playbackAlbum.apply { isSelected = true - setOnClickListener { - playbackModel.song.value?.let { showCurrentAlbum() } - } + setOnClickListener { playbackModel.song.value?.let { showCurrentAlbum() } } } binding.playbackSeekBar.callback = this @@ -136,9 +132,7 @@ class PlaybackPanelFragment : val equalizerIntent = Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL) .putExtra( - AudioEffect.EXTRA_AUDIO_SESSION, - playbackModel.currentAudioSessionId - ) + AudioEffect.EXTRA_AUDIO_SESSION, playbackModel.currentAudioSessionId) .putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) try { @@ -161,9 +155,7 @@ class PlaybackPanelFragment : playbackModel.song.value?.let { song -> navModel.mainNavigateTo( MainNavigationAction.Directions( - MainFragmentDirections.actionShowDetails(song.uid) - ) - ) + MainFragmentDirections.actionShowDetails(song.uid))) } true diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt index 04547a86b..d6e6a1b44 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSheetBehavior.kt @@ -60,7 +60,5 @@ class PlaybackSheetBehavior(context: Context, attributeSet: AttributeS MaterialShapeDrawable(sheetBackgroundDrawable.shapeAppearanceModel).apply { fillColor = sheetBackgroundDrawable.fillColor }, - sheetBackgroundDrawable - ) - ) + sheetBackgroundDrawable)) } 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 bfc4fcb1d..4e95744bf 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -115,44 +115,32 @@ class PlaybackViewModel(application: Application) : playbackManager.play(song, genre, settings) } - /** - * Play an [album]. - */ + /** Play an [album]. */ fun play(album: Album) { playbackManager.play(null, album, settings, false) } - /** - * Play an [artist]. - */ + /** Play an [artist]. */ fun play(artist: Artist) { playbackManager.play(null, artist, settings, false) } - /** - * Play a [genre]. - */ + /** Play a [genre]. */ fun play(genre: Genre) { playbackManager.play(null, genre, settings, false) } - /** - * Shuffle an [album]. - */ + /** Shuffle an [album]. */ fun shuffle(album: Album) { playbackManager.play(null, album, settings, true) } - /** - * Shuffle an [artist]. - */ + /** Shuffle an [artist]. */ fun shuffle(artist: Artist) { playbackManager.play(null, artist, settings, true) } - /** - * Shuffle a [genre]. - */ + /** Shuffle a [genre]. */ fun shuffle(genre: Genre) { playbackManager.play(null, genre, settings, true) } @@ -249,8 +237,8 @@ class PlaybackViewModel(application: Application) : // --- SAVE/RESTORE FUNCTIONS --- /** - * Force save the current [PlaybackStateManager] state to the database. [onDone] - * will be called with true if it was done, or false if an error occurred. + * Force save the current [PlaybackStateManager] state to the database. [onDone] will be called + * with true if it was done, or false if an error occurred. */ fun savePlaybackState(onDone: (Boolean) -> Unit) { viewModelScope.launch { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index 24ab49087..8d7ddcd10 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -131,9 +131,7 @@ class QueueSongViewHolder private constructor(private val binding: ItemQueueSong fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface) elevation = binding.context.getDimen(R.dimen.elevation_normal) }, - backgroundDrawable - ) - ) + backgroundDrawable)) } @SuppressLint("ClickableViewAccessibility") diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index c77232259..728fe2d0d 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -42,9 +42,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe val queueHolder = viewHolder as QueueSongViewHolder return if (queueHolder.isEnabled) { makeFlag( - ItemTouchHelper.ACTION_STATE_DRAG, - ItemTouchHelper.UP or ItemTouchHelper.DOWN - ) or + ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN) or makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START) } else { 0 @@ -134,9 +132,7 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe target: RecyclerView.ViewHolder ) = playbackModel.moveQueueDataItems( - viewHolder.bindingAdapterPosition, - target.bindingAdapterPosition - ) + viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition) 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 71b84894c..b79b1852c 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 @@ -61,18 +61,13 @@ class QueueFragment : ViewBindingFragment(), QueueItemList override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { invalidateDivider() } - } - ) + }) } // --- VIEWMODEL SETUP ---- collectImmediately( - queueModel.queue, - queueModel.index, - playbackModel.isPlaying, - ::updateQueue - ) + queueModel.queue, queueModel.index, playbackModel.isPlaying, ::updateQueue) } override fun onDestroyBinding(binding: FragmentQueueBinding) { @@ -102,7 +97,7 @@ class QueueFragment : ViewBindingFragment(), QueueItemList binding.queueDivider.isInvisible = (binding.queueRecycler.layoutManager as LinearLayoutManager) - .findFirstCompletelyVisibleItemPosition() < 1 + .findFirstCompletelyVisibleItemPosition() < 1 queueModel.finishReplace() @@ -127,6 +122,6 @@ class QueueFragment : ViewBindingFragment(), QueueItemList val binding = requireBinding() binding.queueDivider.isInvisible = (binding.queueRecycler.layoutManager as LinearLayoutManager) - .findFirstCompletelyVisibleItemPosition() < 1 + .findFirstCompletelyVisibleItemPosition() < 1 } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt index e3ed60b26..52fd5a980 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueSheetBehavior.kt @@ -70,10 +70,6 @@ class QueueSheetBehavior(context: Context, attributeSet: AttributeSet? val bars = insets.systemBarInsetsCompat expandedOffset = bars.top + barHeight + barSpacing return insets.replaceSystemBarInsetsCompat( - bars.left, - bars.top, - bars.right, - expandedOffset + bars.bottom - ) + bars.left, bars.top, bars.right, expandedOffset + bars.bottom) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt index 346c460af..a10bc941b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt @@ -59,8 +59,7 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Callback { /** Remove a queue item using it's recyclerview adapter index. */ fun removeQueueDataItem(adapterIndex: Int) { if (adapterIndex <= playbackManager.index || - adapterIndex !in playbackManager.queue.indices - ) { + adapterIndex !in playbackManager.queue.indices) { return } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt index db99fddeb..15d135227 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt @@ -21,12 +21,12 @@ import android.os.Bundle import android.view.LayoutInflater import android.widget.TextView import androidx.appcompat.app.AlertDialog +import kotlin.math.abs import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogPreAmpBinding import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment import org.oxycblt.auxio.util.context -import kotlin.math.abs /** * The dialog for customizing the ReplayGain pre-amp values. @@ -82,7 +82,4 @@ class PreAmpCustomizeDialog : ViewBindingDialogFragment() { getString(R.string.fmt_db_neg, abs(valueDb)) } } - - companion object { - } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt index b7b9e6a6d..cef7527ff 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt @@ -24,13 +24,13 @@ import com.google.android.exoplayer2.audio.BaseAudioProcessor import com.google.android.exoplayer2.metadata.Metadata import com.google.android.exoplayer2.metadata.id3.TextInformationFrame import com.google.android.exoplayer2.metadata.vorbis.VorbisComment +import java.nio.ByteBuffer +import kotlin.math.pow import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.unlikelyToBeNull -import java.nio.ByteBuffer -import kotlin.math.pow /** * An [AudioProcessor] that handles ReplayGain values and their amplification of the audio stream. diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/InternalPlayer.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/InternalPlayer.kt index 14c44f02f..98ad1935e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/InternalPlayer.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/InternalPlayer.kt @@ -92,8 +92,7 @@ interface InternalPlayer { // Not advancing, so don't move the position. 0f }, - creationTime - ) + creationTime) // Equality ignores the creation time to prevent functionally // identical states from being equal. @@ -120,8 +119,7 @@ interface InternalPlayer { // main playing value is paused. isPlaying && isAdvancing, positionMs, - SystemClock.elapsedRealtime() - ) + SystemClock.elapsedRealtime()) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt index ccfd1147f..eae663a11 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt @@ -110,8 +110,7 @@ class PlaybackStateDatabase private constructor(context: Context) : queue = queue, positionMs = rawState.positionMs, repeatMode = rawState.repeatMode, - isShuffled = rawState.isShuffled - ) + isShuffled = rawState.isShuffled) } private fun readRawState(): RawState? { @@ -134,12 +133,11 @@ class PlaybackStateDatabase private constructor(context: Context) : index = cursor.getInt(indexIndex), positionMs = cursor.getLong(posIndex), repeatMode = RepeatMode.fromIntCode(cursor.getInt(repeatModeIndex)) - ?: RepeatMode.NONE, + ?: RepeatMode.NONE, isShuffled = cursor.getInt(shuffleIndex) == 1, songUid = Music.UID.fromString(cursor.getString(songUidIndex)) - ?: return@queryAll null, - parentUid = cursor.getString(parentUidIndex)?.let(Music.UID::fromString) - ) + ?: return@queryAll null, + parentUid = cursor.getString(parentUidIndex)?.let(Music.UID::fromString)) } } @@ -175,8 +173,7 @@ class PlaybackStateDatabase private constructor(context: Context) : repeatMode = state.repeatMode, isShuffled = state.isShuffled, songUid = state.queue[state.index].uid, - parentUid = state.parent?.uid - ) + parentUid = state.parent?.uid) writeRawState(rawState) writeQueue(state.queue) 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 7992a1d82..0f71d700f 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 @@ -17,6 +17,7 @@ package org.oxycblt.auxio.playback.state +import kotlin.math.max import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.oxycblt.auxio.BuildConfig @@ -31,7 +32,6 @@ import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logW -import kotlin.math.max /** * Master class (and possible god object) for the playback state. @@ -268,11 +268,7 @@ class PlaybackStateManager private constructor() { notifyShuffledChanged() } - private fun orderQueue( - settings: Settings, - shuffled: Boolean, - keep: Song? - ) { + private fun orderQueue(settings: Settings, shuffled: Boolean, keep: Song?) { val newIndex: Int if (shuffled) { @@ -369,13 +365,14 @@ class PlaybackStateManager private constructor() { val library = musicStore.library ?: return false val internalPlayer = internalPlayer ?: return false - val state = try { - withContext(Dispatchers.IO) { database.read(library) } - } catch (e: Exception) { - logE("Unable to restore playback state.") - logE(e.stackTraceToString()) - return false - } + val state = + try { + withContext(Dispatchers.IO) { database.read(library) } + } catch (e: Exception) { + logE("Unable to restore playback state.") + logE(e.stackTraceToString()) + return false + } synchronized(this) { if (state != null && (!isInitialized || force)) { @@ -484,8 +481,7 @@ class PlaybackStateManager private constructor() { queue = _queue, positionMs = playerState.calculateElapsedPosition(), isShuffled = isShuffled, - repeatMode = repeatMode - ) + repeatMode = repeatMode) // --- CALLBACKS --- diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/BluetoothHeadsetReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/BluetoothHeadsetReceiver.kt index 62f877f62..17a67c52d 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/BluetoothHeadsetReceiver.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/BluetoothHeadsetReceiver.kt @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2022 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.oxycblt.auxio.playback.system import android.bluetooth.BluetoothProfile @@ -6,18 +23,20 @@ import android.content.Context import android.content.Intent /** - * A [BroadcastReceiver] that handles connections from bluetooth headsets, starting playback if - * they occur. + * A [BroadcastReceiver] that handles connections from bluetooth headsets, starting playback if they + * occur. * @author seijikun, OxygenCobalt */ class BluetoothHeadsetReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == android.bluetooth.BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED) { - val newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) + val newState = + intent.getIntExtra( + BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) if (newState == BluetoothProfile.STATE_CONNECTED) { // TODO: Initialize the service (Permission workflow must be figured out) // Perhaps move this to the internal receivers? } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt index a5371f8fb..94f6e225b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt @@ -140,22 +140,19 @@ class MediaSessionComponent(private val context: Context, private val callback: .putText(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) .putText( MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, - song.album.resolveArtistContents(context) - ) + song.album.resolveArtistContents(context)) .putText(MediaMetadataCompat.METADATA_KEY_AUTHOR, artist) .putText(MediaMetadataCompat.METADATA_KEY_COMPOSER, artist) .putText(MediaMetadataCompat.METADATA_KEY_WRITER, artist) .putText( METADATA_KEY_PARENT, - parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs) - ) + parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs)) .putText(MediaMetadataCompat.METADATA_KEY_GENRE, song.resolveGenreContents(context)) .putText(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title) .putText(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, artist) .putText( MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, - parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs) - ) + parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs)) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.durationMs) song.track?.let { @@ -166,9 +163,7 @@ class MediaSessionComponent(private val context: Context, private val callback: builder.putLong(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER, it.toLong()) } - song.date?.let { - builder.putString(MediaMetadataCompat.METADATA_KEY_DATE, it.toString()) - } + song.date?.let { builder.putString(MediaMetadataCompat.METADATA_KEY_DATE, it.toString()) } // Cover loading is a mess. Android expects you to provide a clean, easy URI for it to // leverage, but Auxio cannot do that as quality-of-life features like scaling or @@ -192,8 +187,7 @@ class MediaSessionComponent(private val context: Context, private val callback: notification.updateMetadata(metadata) callback.onPostNotification(notification, PostingReason.METADATA) } - } - ) + }) } private fun updateQueue(queue: List) { @@ -230,8 +224,7 @@ class MediaSessionComponent(private val context: Context, private val callback: RepeatMode.NONE -> PlaybackStateCompat.REPEAT_MODE_NONE RepeatMode.TRACK -> PlaybackStateCompat.REPEAT_MODE_ONE RepeatMode.ALL -> PlaybackStateCompat.REPEAT_MODE_ALL - } - ) + }) invalidateSecondaryAction() } @@ -242,8 +235,7 @@ class MediaSessionComponent(private val context: Context, private val callback: PlaybackStateCompat.SHUFFLE_MODE_ALL } else { PlaybackStateCompat.SHUFFLE_MODE_NONE - } - ) + }) invalidateSecondaryAction() } @@ -328,8 +320,7 @@ class MediaSessionComponent(private val context: Context, private val callback: playbackManager.reshuffle( shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL || shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP, - settings - ) + settings) } override fun onSkipToQueueItem(id: Long) { @@ -368,30 +359,29 @@ class MediaSessionComponent(private val context: Context, private val callback: // Android 13+ leverages custom actions in the notification. - val extraAction = when (settings.notifAction) { - ActionMode.SHUFFLE -> PlaybackStateCompat.CustomAction.Builder( - PlaybackService.ACTION_INVERT_SHUFFLE, - context.getString(R.string.desc_shuffle), - if (playbackManager.isShuffled) { - R.drawable.ic_shuffle_on_24 - } else { - R.drawable.ic_shuffle_off_24 - } - ) - - else -> PlaybackStateCompat.CustomAction.Builder( - PlaybackService.ACTION_INC_REPEAT_MODE, - context.getString(R.string.desc_change_repeat), - playbackManager.repeatMode.icon - ) - } + val extraAction = + when (settings.notifAction) { + ActionMode.SHUFFLE -> + PlaybackStateCompat.CustomAction.Builder( + PlaybackService.ACTION_INVERT_SHUFFLE, + context.getString(R.string.desc_shuffle), + if (playbackManager.isShuffled) { + R.drawable.ic_shuffle_on_24 + } else { + R.drawable.ic_shuffle_off_24 + }) + else -> + PlaybackStateCompat.CustomAction.Builder( + PlaybackService.ACTION_INC_REPEAT_MODE, + context.getString(R.string.desc_change_repeat), + playbackManager.repeatMode.icon) + } val exitAction = PlaybackStateCompat.CustomAction.Builder( - PlaybackService.ACTION_EXIT, - context.getString(R.string.desc_exit), - R.drawable.ic_close_24 - ) + PlaybackService.ACTION_EXIT, + context.getString(R.string.desc_exit), + R.drawable.ic_close_24) .build() state.addCustomAction(extraAction.build()) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt index 86157ee5d..21cd5a6c9 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt @@ -52,12 +52,10 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes addAction(buildRepeatAction(context, RepeatMode.NONE)) addAction( - buildAction(context, PlaybackService.ACTION_SKIP_PREV, R.drawable.ic_skip_prev_24) - ) + buildAction(context, PlaybackService.ACTION_SKIP_PREV, R.drawable.ic_skip_prev_24)) addAction(buildPlayPauseAction(context, true)) addAction( - buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next_24) - ) + buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next_24)) addAction(buildAction(context, PlaybackService.ACTION_EXIT, R.drawable.ic_close_24)) setStyle(MediaStyle().setMediaSession(sessionToken).setShowActionsInCompactView(1, 2, 3)) @@ -134,10 +132,7 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes ): NotificationCompat.Action { val action = NotificationCompat.Action.Builder( - iconRes, - actionName, - context.newBroadcastPendingIntent(actionName) - ) + iconRes, actionName, context.newBroadcastPendingIntent(actionName)) return action.build() } @@ -146,7 +141,6 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes val CHANNEL_INFO = ChannelInfo( id = BuildConfig.APPLICATION_ID + ".channel.PLAYBACK", - nameRes = R.string.lbl_playback - ) + nameRes = R.string.lbl_playback) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 717546a41..002afa02c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -39,6 +39,8 @@ import com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory import com.google.android.exoplayer2.mediacodec.MediaCodecSelector import com.google.android.exoplayer2.source.DefaultMediaSourceFactory +import kotlin.math.max +import kotlin.math.min import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -57,8 +59,6 @@ import org.oxycblt.auxio.ui.system.ForegroundManager import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.widgets.WidgetComponent import org.oxycblt.auxio.widgets.WidgetProvider -import kotlin.math.max -import kotlin.math.min /** * A service that manages the system-side aspects of playback, such as: @@ -123,10 +123,8 @@ class PlaybackService : handler, audioListener, AudioCapabilities.DEFAULT_AUDIO_CAPABILITIES, - replayGainProcessor - ), - LibflacAudioRenderer(handler, audioListener, replayGainProcessor) - ) + replayGainProcessor), + LibflacAudioRenderer(handler, audioListener, replayGainProcessor)) } // Enable constant bitrate seeking so that certain MP3s/AACs are seekable @@ -141,8 +139,7 @@ class PlaybackService : .setUsage(C.USAGE_MEDIA) .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) .build(), - true - ) + true) .build() player.addListener(this) @@ -227,10 +224,7 @@ class PlaybackService : override fun makeState(durationMs: Long) = InternalPlayer.State.new( - player.playWhenReady, - player.isPlaying, - max(min(player.currentPosition, durationMs), 0) - ) + player.playWhenReady, player.isPlaying, max(min(player.currentPosition, durationMs), 0)) override fun loadSong(song: Song?, play: Boolean) { if (song == null) { @@ -340,8 +334,7 @@ class PlaybackService : override fun onSettingChanged(key: String) { if (key == getString(R.string.set_key_replay_gain) || key == getString(R.string.set_key_pre_amp_with) || - key == getString(R.string.set_key_pre_amp_without) - ) { + key == getString(R.string.set_key_pre_amp_without)) { onTracksChanged(player.currentTracks) } } @@ -353,8 +346,7 @@ class PlaybackService : Intent(event) .putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName) .putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId) - .putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) - ) + .putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)) } /** Stop the foreground state and hide the notification */ @@ -378,9 +370,7 @@ class PlaybackService : is InternalPlayer.Action.RestoreState -> { restoreScope.launch { playbackManager.restoreState( - PlaybackStateDatabase.getInstance(this@PlaybackService), - false - ) + PlaybackStateDatabase.getInstance(this@PlaybackService), false) } } is InternalPlayer.Action.ShuffleAll -> { @@ -475,8 +465,7 @@ class PlaybackService : private fun maybeResumeFromPlug() { if (playbackManager.song != null && settings.headsetAutoplay && - initialHeadsetPlugEventHandled - ) { + initialHeadsetPlugEventHandled) { logD("Device connected, resuming") playbackManager.changePlaying(true) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt index f8cc0db9f..b8ab58ea5 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/ui/StyledSeekBar.kt @@ -20,11 +20,11 @@ package org.oxycblt.auxio.playback.ui import android.content.Context import android.util.AttributeSet import com.google.android.material.slider.Slider +import kotlin.math.max import org.oxycblt.auxio.databinding.ViewSeekBarBinding import org.oxycblt.auxio.playback.formatDurationDs import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.logD -import kotlin.math.max /** * A wrapper around [Slider] that shows not only position and duration values, but also basically 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 799428971..23e3a8c52 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -80,13 +80,14 @@ class SearchFragment : override fun onBindingCreated(binding: FragmentSearchBinding, savedInstanceState: Bundle?) { binding.searchToolbar.apply { - val itemIdToSelect = when (searchModel.filterMode) { - MusicMode.SONGS -> R.id.option_filter_songs - MusicMode.ALBUMS -> R.id.option_filter_albums - MusicMode.ARTISTS -> R.id.option_filter_artists - MusicMode.GENRES -> R.id.option_filter_genres - null -> R.id.option_filter_all - } + val itemIdToSelect = + when (searchModel.filterMode) { + MusicMode.SONGS -> R.id.option_filter_songs + MusicMode.ALBUMS -> R.id.option_filter_albums + MusicMode.ARTISTS -> R.id.option_filter_artists + MusicMode.GENRES -> R.id.option_filter_genres + null -> R.id.option_filter_all + } menu.findItem(itemIdToSelect).isChecked = true @@ -119,11 +120,7 @@ class SearchFragment : collectImmediately(searchModel.searchResults, ::handleResults) collectImmediately( - playbackModel.song, - playbackModel.parent, - playbackModel.isPlaying, - ::handlePlayback - ) + playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::handlePlayback) collect(navModel.exploreNavigationItem, ::handleNavigation) } @@ -148,12 +145,13 @@ class SearchFragment : override fun onItemClick(item: Item) { when (item) { - is Song -> when (settings.libPlaybackMode) { - MusicMode.SONGS -> playbackModel.playFromAll(item) - MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) - MusicMode.ARTISTS -> doArtistDependentAction(item, PickerMode.PLAY) - else -> error("Unexpected playback mode: ${settings.libPlaybackMode}") - } + is Song -> + when (settings.libPlaybackMode) { + MusicMode.SONGS -> playbackModel.playFromAll(item) + MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) + MusicMode.ARTISTS -> doArtistDependentAction(item, PickerMode.PLAY) + else -> error("Unexpected playback mode: ${settings.libPlaybackMode}") + } is MusicParent -> navModel.exploreNavigateTo(item) } } @@ -194,8 +192,7 @@ class SearchFragment : is Artist -> SearchFragmentDirections.actionShowArtist(item.uid) is Genre -> SearchFragmentDirections.actionShowGenre(item.uid) else -> return - } - ) + }) imm.hide() } 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 09b04eb8e..c7ada5d8d 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -23,6 +23,7 @@ import androidx.annotation.IdRes import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import java.text.Normalizer import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -42,7 +43,6 @@ import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.application import org.oxycblt.auxio.util.logD -import java.text.Normalizer /** * The [ViewModel] for search functionality. @@ -87,43 +87,44 @@ class SearchViewModel(application: Application) : logD("Performing search for $query") // Searching can be quite expensive, so get on a co-routine - currentSearchJob = viewModelScope.launch { - val sort = Sort(Sort.Mode.ByName, true) - val results = mutableListOf() + currentSearchJob = + viewModelScope.launch { + val sort = Sort(Sort.Mode.ByName, true) + val results = mutableListOf() - // Note: a filter mode of null means to not filter at all. + // Note: a filter mode of null means to not filter at all. - if (filterMode == null || filterMode == MusicMode.ARTISTS) { - library.artists.filterArtistsBy(query)?.let { artists -> - results.add(Header(R.string.lbl_artists)) - results.addAll(sort.artists(artists)) + if (filterMode == null || filterMode == MusicMode.ARTISTS) { + library.artists.filterArtistsBy(query)?.let { artists -> + results.add(Header(R.string.lbl_artists)) + results.addAll(sort.artists(artists)) + } } - } - if (filterMode == null || filterMode == MusicMode.ALBUMS) { - library.albums.filterAlbumsBy(query)?.let { albums -> - results.add(Header(R.string.lbl_albums)) - results.addAll(sort.albums(albums)) + if (filterMode == null || filterMode == MusicMode.ALBUMS) { + library.albums.filterAlbumsBy(query)?.let { albums -> + results.add(Header(R.string.lbl_albums)) + results.addAll(sort.albums(albums)) + } } - } - if (filterMode == null || filterMode == MusicMode.GENRES) { - library.genres.filterGenresBy(query)?.let { genres -> - results.add(Header(R.string.lbl_genres)) - results.addAll(sort.genres(genres)) + if (filterMode == null || filterMode == MusicMode.GENRES) { + library.genres.filterGenresBy(query)?.let { genres -> + results.add(Header(R.string.lbl_genres)) + results.addAll(sort.genres(genres)) + } } - } - if (filterMode == null || filterMode == MusicMode.SONGS) { - library.songs.filterSongsBy(query)?.let { songs -> - results.add(Header(R.string.lbl_songs)) - results.addAll(sort.songs(songs)) + if (filterMode == null || filterMode == MusicMode.SONGS) { + library.songs.filterSongsBy(query)?.let { songs -> + results.add(Header(R.string.lbl_songs)) + results.addAll(sort.songs(songs)) + } } - } - yield() - _searchResults.value = results - } + yield() + _searchResults.value = results + } } /** @@ -162,14 +163,14 @@ class SearchViewModel(application: Application) : private inline fun List.baseFilterBy(value: String, fallback: (T) -> Boolean) = filter { - // The basic comparison is first by the *normalized* name, as that allows a - // non-unicode search to match with some unicode characters. In an ideal world, we - // would just want to leverage CollationKey, but that is not designed for a contains - // algorithm. If that fails, filter impls have fallback values, primarily around - // sort tags or file names. - it.resolveNameNormalized(application).contains(value, ignoreCase = true) || - fallback(it) - } + // The basic comparison is first by the *normalized* name, as that allows a + // non-unicode search to match with some unicode characters. In an ideal world, we + // would just want to leverage CollationKey, but that is not designed for a contains + // algorithm. If that fails, filter impls have fallback values, primarily around + // sort tags or file names. + it.resolveNameNormalized(application).contains(value, ignoreCase = true) || + fallback(it) + } .ifEmpty { null } private fun Music.resolveNameNormalized(context: Context): String { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt index e9a4a6662..104cf3cbe 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -84,8 +84,7 @@ class AboutFragment : ViewBindingFragment() { binding.aboutTotalDuration.text = getString( R.string.fmt_lib_total_duration, - songs.sumOf { it.durationMs }.formatDurationMs(false) - ) + songs.sumOf { it.durationMs }.formatDurationMs(false)) } private fun updateAlbumCount(albums: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt index d9e9072b2..e3fef5ae4 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -84,11 +84,12 @@ class Settings(private val context: Context, private val callback: Callback? = n if (inner.contains(OldKeys.KEY_SHOW_COVERS) || inner.contains(OldKeys.KEY_QUALITY_COVERS)) { logD("Migrating cover settings") - val mode = when { - !inner.getBoolean(OldKeys.KEY_SHOW_COVERS, true) -> CoverMode.OFF - !inner.getBoolean(OldKeys.KEY_QUALITY_COVERS, true) -> CoverMode.MEDIA_STORE - else -> CoverMode.QUALITY - } + val mode = + when { + !inner.getBoolean(OldKeys.KEY_SHOW_COVERS, true) -> CoverMode.OFF + !inner.getBoolean(OldKeys.KEY_QUALITY_COVERS, true) -> CoverMode.MEDIA_STORE + else -> CoverMode.QUALITY + } inner.edit { putInt(context.getString(R.string.set_key_cover_mode), mode.intCode) @@ -100,11 +101,12 @@ class Settings(private val context: Context, private val callback: Callback? = n if (inner.contains(OldKeys.KEY_ALT_NOTIF_ACTION)) { logD("Migrating ${OldKeys.KEY_ALT_NOTIF_ACTION}") - val mode = if (inner.getBoolean(OldKeys.KEY_ALT_NOTIF_ACTION, false)) { - ActionMode.SHUFFLE - } else { - ActionMode.REPEAT - } + val mode = + if (inner.getBoolean(OldKeys.KEY_ALT_NOTIF_ACTION, false)) { + ActionMode.SHUFFLE + } else { + ActionMode.REPEAT + } inner.edit { putInt(context.getString(R.string.set_key_notif_action), mode.intCode) @@ -125,10 +127,11 @@ class Settings(private val context: Context, private val callback: Callback? = n if (inner.contains(OldKeys.KEY_LIB_PLAYBACK_MODE)) { logD("Migrating ${OldKeys.KEY_LIB_PLAYBACK_MODE}") - val mode = inner.getInt( - OldKeys.KEY_LIB_PLAYBACK_MODE, - IntegerTable.PLAYBACK_MODE_ALL_SONGS - ).migratePlaybackMode() ?: MusicMode.SONGS + val mode = + inner + .getInt(OldKeys.KEY_LIB_PLAYBACK_MODE, IntegerTable.PLAYBACK_MODE_ALL_SONGS) + .migratePlaybackMode() + ?: MusicMode.SONGS inner.edit { putInt(context.getString(R.string.set_key_library_song_playback_mode), mode.intCode) @@ -140,13 +143,13 @@ class Settings(private val context: Context, private val callback: Callback? = n if (inner.contains(OldKeys.KEY_DETAIL_PLAYBACK_MODE)) { logD("Migrating ${OldKeys.KEY_DETAIL_PLAYBACK_MODE}") - val mode = inner.getInt(OldKeys.KEY_DETAIL_PLAYBACK_MODE, Int.MIN_VALUE).migratePlaybackMode() + val mode = + inner.getInt(OldKeys.KEY_DETAIL_PLAYBACK_MODE, Int.MIN_VALUE).migratePlaybackMode() inner.edit { putInt( context.getString(R.string.set_key_detail_song_playback_mode), - mode?.intCode ?: Int.MIN_VALUE - ) + mode?.intCode ?: Int.MIN_VALUE) remove(OldKeys.KEY_DETAIL_PLAYBACK_MODE) apply() } @@ -173,8 +176,7 @@ class Settings(private val context: Context, private val callback: Callback? = n get() = inner.getInt( context.getString(R.string.set_key_theme), - AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - ) + AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) /** Whether the dark theme should be black or not */ val useBlackTheme: Boolean @@ -182,7 +184,8 @@ class Settings(private val context: Context, private val callback: Callback? = n /** The current accent. */ var accent: Accent - get() = Accent.from(inner.getInt(context.getString(R.string.set_key_accent), Accent.DEFAULT)) + get() = + Accent.from(inner.getInt(context.getString(R.string.set_key_accent), Accent.DEFAULT)) set(value) { inner.edit { putInt(context.getString(R.string.set_key_accent), value.index) @@ -194,8 +197,7 @@ class Settings(private val context: Context, private val callback: Callback? = n var libTabs: Array get() = Tab.fromSequence( - inner.getInt(context.getString(R.string.set_key_lib_tabs), Tab.SEQUENCE_DEFAULT) - ) + inner.getInt(context.getString(R.string.set_key_lib_tabs), Tab.SEQUENCE_DEFAULT)) ?: unlikelyToBeNull(Tab.fromSequence(Tab.SEQUENCE_DEFAULT)) set(value) { inner.edit { @@ -216,15 +218,15 @@ class Settings(private val context: Context, private val callback: Callback? = n val actionMode: ActionMode get() = ActionMode.fromIntCode( - inner.getInt(context.getString(R.string.set_key_bar_action), Int.MIN_VALUE) - ) + inner.getInt(context.getString(R.string.set_key_bar_action), Int.MIN_VALUE)) ?: ActionMode.NEXT - /** - * The custom action to display in the notification. - */ + /** The custom action to display in the notification. */ val notifAction: ActionMode - get() = ActionMode.fromIntCode(inner.getInt(context.getString(R.string.set_key_notif_action), Int.MIN_VALUE)) ?: ActionMode.REPEAT + get() = + ActionMode.fromIntCode( + inner.getInt(context.getString(R.string.set_key_notif_action), Int.MIN_VALUE)) + ?: ActionMode.REPEAT /** Whether to resume playback when a headset is connected (may not work well in all cases) */ val headsetAutoplay: Boolean @@ -234,8 +236,7 @@ class Settings(private val context: Context, private val callback: Callback? = n val replayGainMode: ReplayGainMode get() = ReplayGainMode.fromIntCode( - inner.getInt(context.getString(R.string.set_key_replay_gain), Int.MIN_VALUE) - ) + inner.getInt(context.getString(R.string.set_key_replay_gain), Int.MIN_VALUE)) ?: ReplayGainMode.DYNAMIC /** The current ReplayGain pre-amp configuration */ @@ -243,8 +244,7 @@ class Settings(private val context: Context, private val callback: Callback? = n get() = ReplayGainPreAmp( inner.getFloat(context.getString(R.string.set_key_pre_amp_with), 0f), - inner.getFloat(context.getString(R.string.set_key_pre_amp_without), 0f) - ) + inner.getFloat(context.getString(R.string.set_key_pre_amp_without), 0f)) set(value) { inner.edit { putFloat(context.getString(R.string.set_key_pre_amp_with), value.with) @@ -258,10 +258,7 @@ class Settings(private val context: Context, private val callback: Callback? = n get() = MusicMode.fromInt( inner.getInt( - context.getString(R.string.set_key_library_song_playback_mode), - Int.MIN_VALUE - ) - ) + context.getString(R.string.set_key_library_song_playback_mode), Int.MIN_VALUE)) ?: MusicMode.SONGS /** @@ -272,10 +269,7 @@ class Settings(private val context: Context, private val callback: Callback? = n get() = MusicMode.fromInt( inner.getInt( - context.getString(R.string.set_key_detail_song_playback_mode), - Int.MIN_VALUE - ) - ) + context.getString(R.string.set_key_detail_song_playback_mode), Int.MIN_VALUE)) /** Whether shuffle should stay on when a new song is selected. */ val keepShuffle: Boolean @@ -298,7 +292,10 @@ class Settings(private val context: Context, private val callback: Callback? = n /** The strategy used when loading images. */ val coverMode: CoverMode - get() = CoverMode.fromIntCode(inner.getInt(context.getString(R.string.set_key_cover_mode), Int.MIN_VALUE)) ?: CoverMode.MEDIA_STORE + get() = + CoverMode.fromIntCode( + inner.getInt(context.getString(R.string.set_key_cover_mode), Int.MIN_VALUE)) + ?: CoverMode.MEDIA_STORE /** Whether to load all audio files, even ones not considered music. */ val excludeNonMusic: Boolean @@ -311,9 +308,7 @@ class Settings(private val context: Context, private val callback: Callback? = n .mapNotNull { Directory.fromDocumentUri(storageManager, it) } return MusicDirs( - dirs, - inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false) - ) + dirs, inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false)) } /** Set the list of directories that music should be hidden/loaded from. */ @@ -321,12 +316,9 @@ class Settings(private val context: Context, private val callback: Callback? = n inner.edit { putStringSet( context.getString(R.string.set_key_music_dirs), - musicDirs.dirs.map(Directory::toDocumentUri).toSet() - ) + musicDirs.dirs.map(Directory::toDocumentUri).toSet()) putBoolean( - context.getString(R.string.set_key_music_dirs_include), - musicDirs.shouldInclude - ) + context.getString(R.string.set_key_music_dirs_include), musicDirs.shouldInclude) apply() } } @@ -348,14 +340,12 @@ class Settings(private val context: Context, private val callback: Callback? = n var searchFilterMode: MusicMode? get() = MusicMode.fromInt( - inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE) - ) + inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE)) set(value) { inner.edit { putInt( context.getString(R.string.set_key_search_filter), - value?.intCode ?: Int.MIN_VALUE - ) + value?.intCode ?: Int.MIN_VALUE) apply() } } @@ -364,8 +354,7 @@ class Settings(private val context: Context, private val callback: Callback? = n var libSongSort: Sort get() = Sort.fromIntCode( - inner.getInt(context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE) - ) + inner.getInt(context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE)) ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { @@ -378,8 +367,7 @@ class Settings(private val context: Context, private val callback: Callback? = n var libAlbumSort: Sort get() = Sort.fromIntCode( - inner.getInt(context.getString(R.string.set_key_lib_albums_sort), Int.MIN_VALUE) - ) + inner.getInt(context.getString(R.string.set_key_lib_albums_sort), Int.MIN_VALUE)) ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { @@ -392,8 +380,7 @@ class Settings(private val context: Context, private val callback: Callback? = n var libArtistSort: Sort get() = Sort.fromIntCode( - inner.getInt(context.getString(R.string.set_key_lib_artists_sort), Int.MIN_VALUE) - ) + inner.getInt(context.getString(R.string.set_key_lib_artists_sort), Int.MIN_VALUE)) ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { @@ -406,8 +393,7 @@ class Settings(private val context: Context, private val callback: Callback? = n var libGenreSort: Sort get() = Sort.fromIntCode( - inner.getInt(context.getString(R.string.set_key_lib_genres_sort), Int.MIN_VALUE) - ) + inner.getInt(context.getString(R.string.set_key_lib_genres_sort), Int.MIN_VALUE)) ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { @@ -422,10 +408,7 @@ class Settings(private val context: Context, private val callback: Callback? = n var sort = Sort.fromIntCode( inner.getInt( - context.getString(R.string.set_key_detail_album_sort), - Int.MIN_VALUE - ) - ) + context.getString(R.string.set_key_detail_album_sort), Int.MIN_VALUE)) ?: Sort(Sort.Mode.ByDisc, true) // Correct legacy album sort modes to Disc @@ -446,8 +429,7 @@ class Settings(private val context: Context, private val callback: Callback? = n var detailArtistSort: Sort get() = Sort.fromIntCode( - inner.getInt(context.getString(R.string.set_key_detail_artist_sort), Int.MIN_VALUE) - ) + inner.getInt(context.getString(R.string.set_key_detail_artist_sort), Int.MIN_VALUE)) ?: Sort(Sort.Mode.ByDate, false) set(value) { inner.edit { @@ -460,8 +442,7 @@ class Settings(private val context: Context, private val callback: Callback? = n var detailGenreSort: Sort get() = Sort.fromIntCode( - inner.getInt(context.getString(R.string.set_key_detail_genre_sort), Int.MIN_VALUE) - ) + inner.getInt(context.getString(R.string.set_key_detail_genre_sort), Int.MIN_VALUE)) ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/prefs/IntListPreference.kt b/app/src/main/java/org/oxycblt/auxio/settings/prefs/IntListPreference.kt index c18fed2c8..7176b3477 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/prefs/IntListPreference.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/prefs/IntListPreference.kt @@ -26,10 +26,10 @@ import androidx.core.content.res.getTextArrayOrThrow import androidx.preference.DialogPreference import androidx.preference.Preference import androidx.preference.PreferenceViewHolder +import java.lang.reflect.Field import org.oxycblt.auxio.R import org.oxycblt.auxio.util.lazyReflectedField import org.oxycblt.auxio.util.logD -import java.lang.reflect.Field class IntListPreference @JvmOverloads @@ -57,18 +57,13 @@ constructor( init { val prefAttrs = context.obtainStyledAttributes( - attrs, - R.styleable.IntListPreference, - defStyleAttr, - defStyleRes - ) + attrs, R.styleable.IntListPreference, defStyleAttr, defStyleRes) entries = prefAttrs.getTextArrayOrThrow(R.styleable.IntListPreference_entries) values = context.resources.getIntArray( - prefAttrs.getResourceIdOrThrow(R.styleable.IntListPreference_entryValues) - ) + prefAttrs.getResourceIdOrThrow(R.styleable.IntListPreference_entryValues)) val offValueId = prefAttrs.getResourceId(R.styleable.IntListPreference_offValue, -1) if (offValueId > -1) { @@ -152,6 +147,6 @@ constructor( companion object { private val PREFERENCE_DEFAULT_VALUE_FIELD: Field by - lazyReflectedField(Preference::class, "mDefaultValue") + lazyReflectedField(Preference::class, "mDefaultValue") } } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/prefs/PreferenceFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/prefs/PreferenceFragment.kt index bb2616889..2d30c2501 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/prefs/PreferenceFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/prefs/PreferenceFragment.kt @@ -17,8 +17,6 @@ package org.oxycblt.auxio.settings.prefs -import android.app.Activity -import android.os.Build import android.os.Bundle import android.view.View import androidx.annotation.DrawableRes @@ -43,8 +41,6 @@ import org.oxycblt.auxio.util.isNight import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.systemBarInsetsCompat -import java.security.Permission -import java.util.jar.Manifest /** * The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat]. @@ -98,14 +94,20 @@ class PreferenceFragment : PreferenceFragmentCompat() { } is WrappedDialogPreference -> { val context = requireContext() - val directions = when (preference.key) { - context.getString(R.string.set_key_accent) -> SettingsFragmentDirections.goToAccentDialog() - context.getString(R.string.set_key_lib_tabs) -> SettingsFragmentDirections.goToTabDialog() - context.getString(R.string.set_key_pre_amp) -> SettingsFragmentDirections.goToPreAmpDialog() - context.getString(R.string.set_key_music_dirs) -> SettingsFragmentDirections.goToMusicDirsDialog() - getString(R.string.set_key_separators) -> SettingsFragmentDirections.goToSeparatorsDialog() - else -> error("Unexpected dialog key ${preference.key}") - } + val directions = + when (preference.key) { + context.getString(R.string.set_key_accent) -> + SettingsFragmentDirections.goToAccentDialog() + context.getString(R.string.set_key_lib_tabs) -> + SettingsFragmentDirections.goToTabDialog() + context.getString(R.string.set_key_pre_amp) -> + SettingsFragmentDirections.goToPreAmpDialog() + context.getString(R.string.set_key_music_dirs) -> + SettingsFragmentDirections.goToMusicDirsDialog() + getString(R.string.set_key_separators) -> + SettingsFragmentDirections.goToSeparatorsDialog() + else -> error("Unexpected dialog key ${preference.key}") + } findNavController().navigate(directions) } else -> super.onDisplayPreferenceDialog(preference) diff --git a/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt index 0a2d1edd5..9916197c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/AuxioAppBarLayout.kt @@ -49,14 +49,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr if (child != null) { val coordinator = parent as CoordinatorLayout coordinatorLayoutBehavior?.onNestedPreScroll( - coordinator, - this, - coordinator, - 0, - 0, - tConsumed, - 0 - ) + coordinator, this, coordinator, 0, 0, tConsumed, 0) } true diff --git a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt index ace69f94e..21c1b55d8 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetContentBehavior.kt @@ -23,10 +23,10 @@ import android.view.View import android.view.WindowInsets import androidx.coordinatorlayout.widget.CoordinatorLayout import com.google.android.material.bottomsheet.NeoBottomSheetBehavior +import kotlin.math.abs import org.oxycblt.auxio.util.coordinatorLayoutBehavior import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat -import kotlin.math.abs /** * A behavior that automatically re-layouts and re-insets content to align with the parent layout's @@ -127,11 +127,7 @@ class BottomSheetContentBehavior(context: Context, attributeSet: Attri val bars = insets.systemBarInsetsCompat insets.replaceSystemBarInsetsCompat( - bars.left, - bars.top, - bars.right, - (bars.bottom - consumed).coerceAtLeast(0) - ) + bars.left, bars.top, bars.right, (bars.bottom - consumed).coerceAtLeast(0)) } setup = true @@ -145,9 +141,7 @@ class BottomSheetContentBehavior(context: Context, attributeSet: Attri View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.EXACTLY) val contentHeightSpec = View.MeasureSpec.makeMeasureSpec( - parent.measuredHeight - consumed, - View.MeasureSpec.EXACTLY - ) + parent.measuredHeight - consumed, View.MeasureSpec.EXACTLY) child.measure(contentWidthSpec, contentHeightSpec) } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/Accent.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/Accent.kt index c74fc4e12..6213579e9 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/Accent.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/Accent.kt @@ -39,8 +39,7 @@ private val ACCENT_NAMES = R.string.clr_orange, R.string.clr_brown, R.string.clr_grey, - R.string.clr_dynamic - ) + R.string.clr_dynamic) private val ACCENT_THEMES = intArrayOf( @@ -61,7 +60,7 @@ private val ACCENT_THEMES = R.style.Theme_Auxio_Brown, R.style.Theme_Auxio_Grey, R.style.Theme_Auxio_App // Dynamic colors are on the base theme - ) + ) private val ACCENT_BLACK_THEMES = intArrayOf( @@ -82,7 +81,7 @@ private val ACCENT_BLACK_THEMES = R.style.Theme_Auxio_Black_Brown, R.style.Theme_Auxio_Black_Grey, R.style.Theme_Auxio_Black // Dynamic colors are on the base theme - ) + ) private val ACCENT_PRIMARY_COLORS = intArrayOf( @@ -102,8 +101,7 @@ private val ACCENT_PRIMARY_COLORS = R.color.orange_primary, R.color.brown_primary, R.color.grey_primary, - R.color.dynamic_primary - ) + R.color.dynamic_primary) /** * The data object for an accent. In the UI this is known as a "Color Scheme." This can be nominally diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt index 0912591de..d48140555 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt @@ -62,8 +62,7 @@ class AccentCustomizeDialog : Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT)) } else { settings.accent - } - ) + }) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt index 19de0a903..a8b16070c 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt @@ -21,9 +21,9 @@ import android.content.Context import android.util.AttributeSet import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import kotlin.math.max import org.oxycblt.auxio.R import org.oxycblt.auxio.util.getDimenSize -import kotlin.math.max /** * A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollPopupView.kt b/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollPopupView.kt index ae463e58a..e5b07584d 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollPopupView.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollPopupView.kt @@ -165,8 +165,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) centerY + radius, startAngle, sweepAngle, - false - ) + false) } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollRecyclerView.kt index 369880162..e543e3532 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/fastscroll/FastScrollRecyclerView.kt @@ -33,6 +33,7 @@ import androidx.core.view.isInvisible import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import kotlin.math.abs import org.oxycblt.auxio.R import org.oxycblt.auxio.ui.recycler.AuxioRecyclerView import org.oxycblt.auxio.util.getDimenSize @@ -40,7 +41,6 @@ import org.oxycblt.auxio.util.getDrawableCompat import org.oxycblt.auxio.util.isRtl import org.oxycblt.auxio.util.isUnder import org.oxycblt.auxio.util.systemBarInsetsCompat -import kotlin.math.abs /** * A [RecyclerView] that enables better fast-scrolling. This is fundamentally a implementation of @@ -97,9 +97,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr FastScrollPopupView(context).apply { layoutParams = FrameLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) .apply { gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP marginEnd = context.getDimenSize(R.dimen.spacing_small) @@ -174,8 +172,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr override fun onDraw(canvas: Canvas, parent: RecyclerView, state: State) { onPreDraw() } - } - ) + }) // We use a listener instead of overriding onTouchEvent so that we don't conflict with // RecyclerView touch events. @@ -191,8 +188,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr ): Boolean { return onItemTouch(event) } - } - ) + }) } // --- RECYCLERVIEW EVENT MANAGEMENT --- @@ -247,8 +243,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr thumbWidth + popupLayoutParams.leftMargin + popupLayoutParams.rightMargin, - popupLayoutParams.width - ) + popupLayoutParams.width) val heightMeasureSpec = ViewGroup.getChildMeasureSpec( @@ -257,8 +252,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr thumbPadding.bottom + popupLayoutParams.topMargin + popupLayoutParams.bottomMargin, - popupLayoutParams.height - ) + popupLayoutParams.height) popupView.measure(widthMeasureSpec, heightMeasureSpec) } @@ -279,8 +273,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr (thumbTop + thumbAnchorY - popupAnchorY) .coerceAtLeast(thumbPadding.top + popupLayoutParams.topMargin) .coerceAtMost( - height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight - ) + height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight) popupView.layout(popupLeft, popupTop, popupLeft + popupWidth, popupTop + popupHeight) } @@ -355,8 +348,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr MotionEvent.ACTION_MOVE -> { if (!dragging && thumbView.isUnder(downX, thumbView.top.toFloat(), minTouchTargetSize) && - abs(eventY - downY) > touchSlop - ) { + abs(eventY - downY) > touchSlop) { if (thumbView.isUnder(downX, downY, minTouchTargetSize)) { dragStartY = lastY dragStartThumbOffset = thumbOffset diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fragment/MusicFragment.kt b/app/src/main/java/org/oxycblt/auxio/ui/fragment/MusicFragment.kt index b6d633240..5e524ba99 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fragment/MusicFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/fragment/MusicFragment.kt @@ -49,8 +49,8 @@ abstract class MusicFragment : ViewBindingFragment() { protected val navModel: NavigationViewModel by activityViewModels() /** - * Run the UI flow to perform a specific [PickerMode] action with a particular - * artist from [song]. + * Run the UI flow to perform a specific [PickerMode] action with a particular artist from + * [song]. */ fun doArtistDependentAction(song: Song, mode: PickerMode) { if (song.artists.size == 1) { @@ -61,24 +61,18 @@ abstract class MusicFragment : ViewBindingFragment() { } else { navModel.mainNavigateTo( MainNavigationAction.Directions( - MainFragmentDirections.actionPickArtist(song.uid, mode) - ) - ) + MainFragmentDirections.actionPickArtist(song.uid, mode))) } } - /** - * Run the UI flow to navigate to a particular artist from [album]. - */ + /** Run the UI flow to navigate to a particular artist from [album]. */ fun navigateToArtist(album: Album) { if (album.artists.size == 1) { navModel.exploreNavigateTo(album.artists[0]) } else { navModel.mainNavigateTo( MainNavigationAction.Directions( - MainFragmentDirections.actionPickArtist(album.uid, PickerMode.SHOW) - ) - ) + MainFragmentDirections.actionPickArtist(album.uid, PickerMode.SHOW))) } } @@ -108,9 +102,7 @@ abstract class MusicFragment : ViewBindingFragment() { R.id.action_song_detail -> { navModel.mainNavigateTo( MainNavigationAction.Directions( - MainFragmentDirections.actionShowDetails(song.uid) - ) - ) + MainFragmentDirections.actionShowDetails(song.uid))) } else -> { error("Unexpected menu item selected") diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingDialogFragment.kt b/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingDialogFragment.kt index 48cc24162..424296c20 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingDialogFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingDialogFragment.kt @@ -26,10 +26,10 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.viewbinding.ViewBinding import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.unlikelyToBeNull import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty +import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.unlikelyToBeNull /** * A dialog fragment enabling ViewBinding inflation and usage across the dialog fragment lifecycle. diff --git a/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingFragment.kt b/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingFragment.kt index 5ab043a4c..5f7a009a6 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/fragment/ViewBindingFragment.kt @@ -23,10 +23,10 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.viewbinding.ViewBinding -import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.unlikelyToBeNull import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty +import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.unlikelyToBeNull /** * A fragment enabling ViewBinding inflation and usage across the fragment lifecycle. diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/Adapters.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/Adapters.kt index b744fadd7..abd66548d 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/Adapters.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/Adapters.kt @@ -106,8 +106,6 @@ abstract class DialogViewHolder(root: View) : RecyclerView.ViewHolder(root) { // Actually make the item full-width, which it won't be in dialogs root.layoutParams = RecyclerView.LayoutParams( - RecyclerView.LayoutParams.MATCH_PARENT, - RecyclerView.LayoutParams.WRAP_CONTENT - ) + RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt index a4c191cf1..3edd521d5 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/AuxioRecyclerView.kt @@ -49,8 +49,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr initialPadding.left, initialPadding.top, initialPadding.right, - initialPadding.bottom + insets.systemBarInsetsCompat.bottom - ) + initialPadding.bottom + insets.systemBarInsetsCompat.bottom) return insets } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt index 38cf7b5ed..8408c487d 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt @@ -126,8 +126,7 @@ class SyncListDiffer( throw AssertionError() } } - } - ) + }) field = newList result.dispatchUpdatesTo(updateCallback) diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt index 78eed8c44..77813e9cc 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/DialogRecyclerView.kt @@ -68,15 +68,13 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr ViewGroup.getChildMeasureSpec( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 0, - divider.layoutParams.width - ) + divider.layoutParams.width) val heightMeasureSpec = ViewGroup.getChildMeasureSpec( MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), 0, - divider.layoutParams.height - ) + divider.layoutParams.height) divider.measure(widthMeasureSpec, heightMeasureSpec) } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt index 50667f228..91507a302 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt @@ -63,8 +63,7 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) : val DIFFER = object : SimpleItemCallback() { override fun areContentsTheSame(oldItem: Song, newItem: Song) = - oldItem.rawName == newItem.rawName && - oldItem.areArtistContentsTheSame(newItem) + oldItem.rawName == newItem.rawName && oldItem.areArtistContentsTheSame(newItem) } } } @@ -119,16 +118,16 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin binding.parentImage.bind(item) binding.parentName.text = item.resolveName(binding.context) - binding.parentInfo.text = if (item.songs.isNotEmpty()) { - binding.context.getString( - R.string.fmt_two, - binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size), - binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size) - ) - } else { - // Artist has no songs, only display an album count. - binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size) - } + binding.parentInfo.text = + if (item.songs.isNotEmpty()) { + binding.context.getString( + R.string.fmt_two, + binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size), + binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) + } else { + // Artist has no songs, only display an album count. + binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size) + } // binding.parentMenu.setOnClickListener { listener.onOpenMenu(item, it) } binding.root.setOnLongClickListener { @@ -172,8 +171,7 @@ class GenreViewHolder private constructor(private val binding: ItemParentBinding binding.context.getString( R.string.fmt_two, binding.context.getPlural(R.plurals.fmt_artist_count, item.artists.size), - binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size) - ) + binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)) // binding.parentMenu.setOnClickListener { listener.onOpenMenu(item, it) } binding.root.setOnLongClickListener { listener.onOpenMenu(item, it) diff --git a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt index 9d7a17588..4ff681e6f 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt @@ -37,9 +37,9 @@ import androidx.annotation.PluralsRes import androidx.annotation.Px import androidx.annotation.StringRes import androidx.core.content.ContextCompat +import kotlin.reflect.KClass import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.MainActivity -import kotlin.reflect.KClass /** Shortcut to get a [LayoutInflater] from a [Context] */ val Context.inflater: LayoutInflater @@ -152,8 +152,7 @@ fun Context.newMainPendingIntent(): PendingIntent = this, IntegerTable.REQUEST_CODE, Intent(this, MainActivity::class.java), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) /** Create a broadcast [PendingIntent] */ fun Context.newBroadcastPendingIntent(what: String): PendingIntent = @@ -161,5 +160,4 @@ fun Context.newBroadcastPendingIntent(what: String): PendingIntent = this, IntegerTable.REQUEST_CODE, Intent(what).setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) diff --git a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt index f71cd225d..24ca26d1f 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt @@ -231,8 +231,7 @@ val WindowInsets.systemGestureInsetsCompat: Insets Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { Insets.max( getCompatInsets(WindowInsets.Type.systemGestures()), - getCompatInsets(WindowInsets.Type.systemBars()) - ) + getCompatInsets(WindowInsets.Type.systemBars())) } Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> { @Suppress("DEPRECATION") @@ -247,8 +246,7 @@ fun WindowInsets.getSystemWindowCompatInsets() = systemWindowInsetLeft, systemWindowInsetTop, systemWindowInsetRight, - systemWindowInsetBottom - ) + systemWindowInsetBottom) @Suppress("DEPRECATION") @RequiresApi(Build.VERSION_CODES.Q) @@ -272,8 +270,7 @@ fun WindowInsets.replaceSystemBarInsetsCompat( WindowInsets.Builder(this) .setInsets( WindowInsets.Type.systemBars(), - Insets.of(left, top, right, bottom).toPlatformInsets() - ) + Insets.of(left, top, right, bottom).toPlatformInsets()) .build() } else -> { diff --git a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt index 427dfc3d9..2123e8ab8 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt @@ -50,14 +50,14 @@ private val Any.autoTag: String get() = "Auxio.${this::class.simpleName ?: "Anonymous Object"}" /** - * Note: If you are politely forking this project while keeping the source open, you can ignore - * the following passage. If not, give me a moment of your time. + * Note: If you are politely forking this project while keeping the source open, you can ignore the + * following passage. If not, give me a moment of your time. * * Consider what you are doing with your life, plagiarizers. Do you want to live a fulfilling - * existence on this planet? Or do you want to spend your life taking work others did and making - * it objectively worse so you could arbitrage a fraction of a penny on every AdMob impression you - * get? You could do so many great things if you simply had the courage to come up with an idea of - * your own. + * existence on this planet? Or do you want to spend your life taking work others did and making it + * objectively worse so you could arbitrage a fraction of a penny on every AdMob impression you get? + * You could do so many great things if you simply had the courage to come up with an idea of your + * own. * * If you still want to go on, I guess the only thing I can say is this: * @@ -82,13 +82,11 @@ private val Any.autoTag: String @Suppress("KotlinConstantConditions") private fun basedCopyleftNotice(): Boolean { if (BuildConfig.APPLICATION_ID != "org.oxycblt.auxio" && - BuildConfig.APPLICATION_ID != "org.oxycblt.auxio.debug" - ) { + BuildConfig.APPLICATION_ID != "org.oxycblt.auxio.debug") { Log.d( "Auxio Project", "Friendly reminder: Auxio is licensed under the " + - "GPLv3 and all derivative apps must be made open source!" - ) + "GPLv3 and all derivative apps must be made open source!") return true } diff --git a/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt index 74bdc0066..198921ef7 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt @@ -18,10 +18,10 @@ package org.oxycblt.auxio.util import android.os.Looper -import org.oxycblt.auxio.BuildConfig import java.lang.reflect.Field import java.lang.reflect.Method import kotlin.reflect.KClass +import org.oxycblt.auxio.BuildConfig /** Assert that we are on a background thread. */ fun requireBackgroundThread() { diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt index dda2ee12d..74a9a0680 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt @@ -126,8 +126,7 @@ private fun RemoteViews.applyCover( setImageViewBitmap(R.id.widget_cover, state.cover) setContentDescription( R.id.widget_cover, - context.getString(R.string.desc_album_cover, state.song.album.resolveName(context)) - ) + context.getString(R.string.desc_album_cover, state.song.album.resolveName(context))) } else { setImageViewResource(R.id.widget_cover, R.drawable.ic_remote_default_cover_24) setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover)) @@ -145,8 +144,7 @@ private fun RemoteViews.applyPlayPauseControls( setOnClickPendingIntent( R.id.widget_play_pause, - context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE) - ) + context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE)) // Like the Android 13 media controls, use a circular fab when paused, and a squircle fab // when playing. @@ -175,14 +173,10 @@ private fun RemoteViews.applyBasicControls( applyPlayPauseControls(context, state) setOnClickPendingIntent( - R.id.widget_skip_prev, - context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_PREV) - ) + R.id.widget_skip_prev, context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_PREV)) setOnClickPendingIntent( - R.id.widget_skip_next, - context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_NEXT) - ) + R.id.widget_skip_next, context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_NEXT)) return this } @@ -195,13 +189,11 @@ private fun RemoteViews.applyFullControls( setOnClickPendingIntent( R.id.widget_repeat, - context.newBroadcastPendingIntent(PlaybackService.ACTION_INC_REPEAT_MODE) - ) + context.newBroadcastPendingIntent(PlaybackService.ACTION_INC_REPEAT_MODE)) setOnClickPendingIntent( R.id.widget_shuffle, - context.newBroadcastPendingIntent(PlaybackService.ACTION_INVERT_SHUFFLE) - ) + context.newBroadcastPendingIntent(PlaybackService.ACTION_INVERT_SHUFFLE)) val shuffleRes = when { diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index 1a3e74481..9a03852e8 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -22,6 +22,7 @@ import android.graphics.Bitmap import android.os.Build import coil.request.ImageRequest import coil.transform.RoundedCornersTransformation +import kotlin.math.sqrt import org.oxycblt.auxio.R import org.oxycblt.auxio.image.BitmapProvider import org.oxycblt.auxio.image.extractor.SquareFrameTransform @@ -33,7 +34,6 @@ import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.getDimenSize import org.oxycblt.auxio.util.logD -import kotlin.math.sqrt /** * A wrapper around each [WidgetProvider] that plugs into the main Auxio process and updates the @@ -109,8 +109,7 @@ class WidgetComponent(private val context: Context) : .size(computeSize(sw, sh, 10f)) .transformations( SquareFrameTransform.INSTANCE, - RoundedCornersTransformation(cornerRadius.toFloat()) - ) + RoundedCornersTransformation(cornerRadius.toFloat())) } else { // Divide by two to really make sure we aren't hitting the memory limit. builder.size(computeSize(sw, sh, 2f)) @@ -121,8 +120,7 @@ class WidgetComponent(private val context: Context) : val state = WidgetState(song, bitmap, isPlaying, repeatMode, isShuffled) widget.update(context, state) } - } - ) + }) } private fun computeSize(sw: Int, sh: Int, modifier: Float) = @@ -147,8 +145,7 @@ class WidgetComponent(private val context: Context) : override fun onRepeatChanged(repeatMode: RepeatMode) = update() override fun onSettingChanged(key: String) { if (key == context.getString(R.string.set_key_cover_mode) || - key == context.getString(R.string.set_key_round_mode) - ) { + key == context.getString(R.string.set_key_round_mode)) { update() } } diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt index 3bf944fe8..928c924d1 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -60,8 +60,7 @@ class WidgetProvider : AppWidgetProvider() { SizeF(180f, 152f) to createSmallWidget(context, state), SizeF(272f, 152f) to createWideWidget(context, state), SizeF(180f, 272f) to createMediumWidget(context, state), - SizeF(272f, 272f) to createLargeWidget(context, state) - ) + SizeF(272f, 272f) to createLargeWidget(context, state)) val awm = AppWidgetManager.getInstance(context) @@ -70,9 +69,7 @@ class WidgetProvider : AppWidgetProvider() { } catch (e: Exception) { logW("Unable to update widget: $e") awm.updateAppWidget( - ComponentName(context, this::class.java), - createDefaultWidget(context) - ) + ComponentName(context, this::class.java), createDefaultWidget(context)) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 596877463..b78c6fc6e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -170,13 +170,13 @@ Library tabs Change visibility and order of library tabs Hide collaborators - Only show artists that are directly credited on an album in the library + Only show artists that are directly credited on an album (works best on well-tagged libraries) Album covers Off Fast High quality Round mode - Enable rounded corners on additional UI elements (Requires album covers to be rounded) + Enable rounded corners on additional UI elements (requires album covers to be rounded) Custom playback bar action Skip to next @@ -217,13 +217,13 @@ Restore the previously saved playback state (if any) Content - Reload music - Reload the music library, using the cache whenever possible + Refresh music + Reload the music library, using cached tags when possible Rescan music - Reload the music library and re-create the cache (Slower, but more complete) + Clear the tag cache and fully reload the music library (slower, but more complete) Automatic reloading - Reload the music library whenever it changes (Requires persistent notification) + Reload the music library whenever it changes (requires persistent notification) Music folders Manage where music should be loaded from