From 09823d7829062e64a85add09c2f185550afab2cb Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Thu, 8 Sep 2022 20:57:46 -0600 Subject: [PATCH] all: reformat code Simultaniously reformat code using ktlint and ktfmt. --- app/build.gradle | 9 +- .../java/org/oxycblt/auxio/IntegerTable.kt | 34 +++++++ .../java/org/oxycblt/auxio/MainFragment.kt | 10 +- .../oxycblt/auxio/detail/DetailViewModel.kt | 9 +- .../oxycblt/auxio/detail/SongDetailDialog.kt | 2 +- .../detail/recycler/AlbumDetailAdapter.kt | 2 +- .../detail/recycler/ArtistDetailAdapter.kt | 12 +-- .../auxio/detail/recycler/DetailAdapter.kt | 3 +- .../detail/recycler/GenreDetailAdapter.kt | 2 +- .../org/oxycblt/auxio/home/HomeFragment.kt | 14 ++- .../auxio/home/list/AlbumListFragment.kt | 6 +- .../auxio/home/list/ArtistListFragment.kt | 2 +- .../auxio/home/list/GenreListFragment.kt | 2 +- .../auxio/home/list/SongListFragment.kt | 4 +- .../java/org/oxycblt/auxio/home/tabs/Tab.kt | 5 + .../org/oxycblt/auxio/image/Components.kt | 4 +- .../java/org/oxycblt/auxio/music/Music.kt | 27 +++--- .../org/oxycblt/auxio/music/MusicStore.kt | 6 ++ .../java/org/oxycblt/auxio/music/MusicUtil.kt | 12 ++- .../org/oxycblt/auxio/music/MusicViewModel.kt | 2 + .../oxycblt/auxio/music/StorageFramework.kt | 5 +- .../auxio/music/extractor/CacheLayer.kt | 23 ++++- .../auxio/music/extractor/MediaStoreLayer.kt | 88 +++++++++++------- .../auxio/music/extractor/MetadataLayer.kt | 44 +++++---- .../auxio/music/extractor/ParsingUtil.kt | 53 +++++++---- .../org/oxycblt/auxio/music/system/Indexer.kt | 20 ++-- .../auxio/music/system/IndexerService.kt | 7 +- .../auxio/playback/ForcedLTRFrameLayout.kt | 2 +- .../auxio/playback/PlaybackBarFragment.kt | 2 +- .../oxycblt/auxio/playback/PlaybackMode.kt | 3 + .../auxio/playback/PlaybackPanelFragment.kt | 2 +- .../auxio/playback/PlaybackViewModel.kt | 8 +- .../oxycblt/auxio/playback/StyledSeekBar.kt | 6 +- .../auxio/playback/queue/QueueAdapter.kt | 15 +-- .../playback/queue/QueueSheetBehavior.kt | 6 +- .../auxio/playback/replaygain/ReplayGain.kt | 4 +- .../playback/state/PlaybackStateDatabase.kt | 5 +- .../playback/state/PlaybackStateManager.kt | 5 + .../oxycblt/auxio/search/SearchViewModel.kt | 1 + .../oxycblt/auxio/settings/AboutFragment.kt | 2 +- .../org/oxycblt/auxio/settings/Settings.kt | 41 +++++++- .../oxycblt/auxio/settings/SettingsCompat.kt | 58 ------------ .../oxycblt/auxio/ui/AuxioSheetBehavior.kt | 3 +- .../oxycblt/auxio/ui/NavigationViewModel.kt | 6 ++ .../main/java/org/oxycblt/auxio/ui/Sort.kt | 2 +- .../ui/fastscroll/FastScrollRecyclerView.kt | 1 - .../oxycblt/auxio/ui/recycler/ViewHolders.kt | 12 +-- .../org/oxycblt/auxio/util/PrimitiveUtil.kt | 3 +- .../java/org/oxycblt/auxio/widgets/Forms.kt | 1 + .../oxycblt/auxio/widgets/WidgetComponent.kt | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 6 ++ gradlew.bat | 14 +-- 55 files changed, 385 insertions(+), 236 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt diff --git a/app/build.gradle b/app/build.gradle index 7ea0c6819..65ea72135 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -120,7 +120,14 @@ dependencies { spotless { kotlin { target "src/**/*.kt" - ktfmt("0.37").dropboxStyle() + + // ktlint does checking, while ktfmt actually does formatting + ktlint() + ktfmt().dropboxStyle() licenseHeaderFile("NOTICE") } } + +afterEvaluate { + preDebugBuild.dependsOn spotlessApply +} \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt index 75b8a01e1..914ffbef8 100644 --- a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt +++ b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt @@ -21,51 +21,70 @@ package org.oxycblt.auxio object IntegerTable { /** SongViewHolder */ const val VIEW_TYPE_SONG = 0xA000 + /** AlbumViewHolder */ const val VIEW_TYPE_ALBUM = 0xA001 + /** ArtistViewHolder */ const val VIEW_TYPE_ARTIST = 0xA002 + /** GenreViewHolder */ const val VIEW_TYPE_GENRE = 0xA003 + /** HeaderViewHolder */ const val VIEW_TYPE_HEADER = 0xA004 + /** SortHeaderViewHolder */ const val VIEW_TYPE_SORT_HEADER = 0xA005 + /** AlbumDetailViewHolder */ const val VIEW_TYPE_ALBUM_DETAIL = 0xA006 + /** AlbumSongViewHolder */ const val VIEW_TYPE_ALBUM_SONG = 0xA007 + /** ArtistDetailViewHolder */ const val VIEW_TYPE_ARTIST_DETAIL = 0xA008 + /** ArtistAlbumViewHolder */ const val VIEW_TYPE_ARTIST_ALBUM = 0xA009 + /** ArtistSongViewHolder */ const val VIEW_TYPE_ARTIST_SONG = 0xA00A + /** GenreDetailViewHolder */ const val VIEW_TYPE_GENRE_DETAIL = 0xA00B + /** DiscHeaderViewHolder */ const val VIEW_TYPE_DISC_HEADER = 0xA00C /** "Music playback" notification code */ const val PLAYBACK_NOTIFICATION_CODE = 0xA0A0 + /** "Music loading" notification code */ const val INDEXER_NOTIFICATION_CODE = 0xA0A1 + /** Intent request code */ const val REQUEST_CODE = 0xA0C0 /** RepeatMode.NONE */ const val REPEAT_MODE_NONE = 0xA100 + /** RepeatMode.ALL */ const val REPEAT_MODE_ALL = 0xA101 + /** RepeatMode.TRACK */ const val REPEAT_MODE_TRACK = 0xA102 /** PlaybackMode.IN_GENRE */ const val PLAYBACK_MODE_IN_GENRE = 0xA103 + /** PlaybackMode.IN_ARTIST */ const val PLAYBACK_MODE_IN_ARTIST = 0xA104 + /** PlaybackMode.IN_ALBUM */ const val PLAYBACK_MODE_IN_ALBUM = 0xA105 + /** PlaybackMode.ALL_SONGS */ const val PLAYBACK_MODE_ALL_SONGS = 0xA106 @@ -73,10 +92,13 @@ object IntegerTable { // const val DISPLAY_MODE_NONE = 0xA107 /** DisplayMode.SHOW_GENRES */ const val DISPLAY_MODE_SHOW_GENRES = 0xA108 + /** DisplayMode.SHOW_ARTISTS */ const val DISPLAY_MODE_SHOW_ARTISTS = 0xA109 + /** DisplayMode.SHOW_ALBUMS */ const val DISPLAY_MODE_SHOW_ALBUMS = 0xA10A + /** DisplayMode.SHOW_SONGS */ const val DISPLAY_MODE_SHOW_SONGS = 0xA10B @@ -85,20 +107,28 @@ object IntegerTable { /** Sort.ByName */ const val SORT_BY_NAME = 0xA10C + /** Sort.ByArtist */ const val SORT_BY_ARTIST = 0xA10D + /** Sort.ByAlbum */ const val SORT_BY_ALBUM = 0xA10E + /** Sort.ByYear */ const val SORT_BY_YEAR = 0xA10F + /** Sort.ByDuration */ const val SORT_BY_DURATION = 0xA114 + /** Sort.ByCount */ const val SORT_BY_COUNT = 0xA115 + /** Sort.ByDisc */ const val SORT_BY_DISC = 0xA116 + /** Sort.ByTrack */ const val SORT_BY_TRACK = 0xA117 + /** Sort.ByDateAdded */ const val SORT_BY_DATE_ADDED = 0xA118 @@ -106,15 +136,19 @@ object IntegerTable { // const val REPLAY_GAIN_MODE_OFF = 0xA110 /** ReplayGainMode.Track */ const val REPLAY_GAIN_MODE_TRACK = 0xA111 + /** ReplayGainMode.Album */ const val REPLAY_GAIN_MODE_ALBUM = 0xA112 + /** ReplayGainMode.Dynamic */ const val REPLAY_GAIN_MODE_DYNAMIC = 0xA113 /** BarAction.Next */ const val BAR_ACTION_NEXT = 0xA119 + /** BarAction.Repeat */ const val BAR_ACTION_REPEAT = 0xA11A + /** BarAction.Shuffle */ const val BAR_ACTION_SHUFFLE = 0xA11B } diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 1fc5e403c..a642f4f23 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -42,7 +42,15 @@ import org.oxycblt.auxio.playback.queue.QueueSheetBehavior import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.fragment.ViewBindingFragment -import org.oxycblt.auxio.util.* +import org.oxycblt.auxio.util.androidActivityViewModels +import org.oxycblt.auxio.util.collect +import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.context +import org.oxycblt.auxio.util.coordinatorLayoutBehavior +import org.oxycblt.auxio.util.getAttrColorCompat +import org.oxycblt.auxio.util.getDimen +import org.oxycblt.auxio.util.systemBarInsetsCompat +import org.oxycblt.auxio.util.unlikelyToBeNull /** * A wrapper around the home fragment that shows the playback fragment and controls the more 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 fe72061cd..c3e044349 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -28,7 +28,14 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.MimeType +import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.music.MusicStore +import org.oxycblt.auxio.music.ReleaseType +import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.recycler.Header 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 6b6f7ca24..8b820e579 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt @@ -26,10 +26,10 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogSongDetailBinding +import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.collectImmediately -import org.oxycblt.auxio.music.formatDurationMs /** * A dialog displayed when "View properties" is selected on a song, showing more information about 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 16da37b57..c3fd0f677 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 @@ -29,12 +29,12 @@ import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding import org.oxycblt.auxio.detail.DiscHeader import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.ui.recycler.IndicatorAdapter import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.util.context -import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.inflater 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 98cb49071..afae6fa43 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 @@ -143,10 +143,8 @@ private class ArtistDetailViewHolder private constructor(private val binding: It } } -private class ArtistAlbumViewHolder -private constructor( - private val binding: ItemParentBinding, -) : IndicatorAdapter.ViewHolder(binding.root) { +private class ArtistAlbumViewHolder private constructor(private val binding: ItemParentBinding) : + IndicatorAdapter.ViewHolder(binding.root) { fun bind(item: Album, listener: MenuItemListener) { binding.parentImage.bind(item) binding.parentName.text = item.resolveName(binding.context) @@ -178,10 +176,8 @@ private constructor( } } -private class ArtistSongViewHolder -private constructor( - private val binding: ItemSongBinding, -) : IndicatorAdapter.ViewHolder(binding.root) { +private class ArtistSongViewHolder private constructor(private val binding: ItemSongBinding) : + IndicatorAdapter.ViewHolder(binding.root) { fun bind(item: Song, listener: MenuItemListener) { binding.songAlbumCover.bind(item) binding.songName.text = item.resolveName(binding.context) 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 ae9d7ac98..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 @@ -83,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 8df39f973..c09f569fb 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 @@ -25,11 +25,11 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.ui.recycler.SongViewHolder import org.oxycblt.auxio.util.context -import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.inflater 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 c8ecfbb6a..22141af01 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -45,7 +45,12 @@ import org.oxycblt.auxio.home.list.AlbumListFragment import org.oxycblt.auxio.home.list.ArtistListFragment import org.oxycblt.auxio.home.list.GenreListFragment import org.oxycblt.auxio.home.list.SongListFragment -import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.music.MusicViewModel +import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.system.Indexer import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.DisplayMode @@ -53,7 +58,12 @@ import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.fragment.ViewBindingFragment -import org.oxycblt.auxio.util.* +import org.oxycblt.auxio.util.androidActivityViewModels +import org.oxycblt.auxio.util.collect +import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.getColorCompat +import org.oxycblt.auxio.util.lazyReflectedField +import org.oxycblt.auxio.util.logD /** * The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each 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 307e43bc8..722561fdb 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,11 +21,13 @@ import android.os.Bundle import android.text.format.DateUtils import android.view.View import android.view.ViewGroup -import java.util.* +import java.util.Formatter import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.music.formatDurationMs +import org.oxycblt.auxio.music.secsToMs import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.recycler.AlbumViewHolder @@ -34,8 +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 org.oxycblt.auxio.music.formatDurationMs -import org.oxycblt.auxio.music.secsToMs /** * A [HomeListFragment] for showing a list of [Album]s. diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt index 65abed109..7077c3bf4 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -24,6 +24,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.recycler.ArtistViewHolder @@ -32,7 +33,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 org.oxycblt.auxio.music.formatDurationMs /** * A [HomeListFragment] for showing a list of [Artist]s. diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt index e21f08496..2e123197a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -24,6 +24,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.recycler.GenreViewHolder @@ -32,7 +33,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 org.oxycblt.auxio.music.formatDurationMs /** * A [HomeListFragment] for showing a list of [Genre]s. 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 22383810e..08e4ab3cd 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 @@ -26,6 +26,8 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.formatDurationMs +import org.oxycblt.auxio.music.secsToMs import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Sort @@ -36,8 +38,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 org.oxycblt.auxio.music.formatDurationMs -import org.oxycblt.auxio.music.secsToMs /** * A [HomeListFragment] for showing a list of [Song]s. 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 3108bcd4b..1bfaa1f8d 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 @@ -17,6 +17,10 @@ package org.oxycblt.auxio.home.tabs +import org.oxycblt.auxio.home.tabs.Tab.Companion.fromSequence +import org.oxycblt.auxio.home.tabs.Tab.Companion.toSequence +import org.oxycblt.auxio.home.tabs.Tab.Invisible +import org.oxycblt.auxio.home.tabs.Tab.Visible import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.util.logE @@ -50,6 +54,7 @@ sealed class Tab(open val mode: DisplayMode) { companion object { /** The length a well-formed tab sequence should be */ private const val SEQUENCE_LEN = 4 + /** The default tab sequence, represented in integer form */ const val SEQUENCE_DEFAULT = 0b1000_1001_1010_1011_0100 diff --git a/app/src/main/java/org/oxycblt/auxio/image/Components.kt b/app/src/main/java/org/oxycblt/auxio/image/Components.kt index 094093408..e8e552f85 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/Components.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/Components.kt @@ -82,7 +82,7 @@ class ArtistImageFetcher private constructor( private val context: Context, private val size: Size, - private val artist: Artist, + private val artist: Artist ) : BaseFetcher() { override suspend fun fetch(): FetchResult? { val albums = Sort(Sort.Mode.ByName, true).albums(artist.albums) @@ -104,7 +104,7 @@ class GenreImageFetcher private constructor( private val context: Context, private val size: Size, - private val genre: Genre, + private val genre: Genre ) : BaseFetcher() { override suspend fun fetch(): FetchResult? { // Genre logic is the most complicated, as we want to ensure album cover variation (i.e 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 a2e6270e7..11106a55a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -30,6 +30,7 @@ import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R +import org.oxycblt.auxio.music.Date.Companion.from import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.inRangeOrNull @@ -53,14 +54,16 @@ sealed class Music : Item { * fast-scrolling. */ val sortName: String? - get() = 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 - } - } + get() = + 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 + } + } /** * Resolve a name from it's raw form to a form suitable to be shown in a ui. Ex. "unknown" would @@ -185,6 +188,7 @@ class Song constructor(raw: Raw) : Music() { val disc = raw.disc private var _album: Album? = null + /** The album of this song. */ val album: Album get() = unlikelyToBeNull(_album) @@ -212,6 +216,7 @@ class Song constructor(raw: Raw) : Music() { artistName ?: album.artist.resolveName(context) private val _genres: MutableList = mutableListOf() + /** * The genres of this song. Most often one, but there could be multiple. There will always be at * least one genre, even if it is an "unknown genre" instance. @@ -327,6 +332,7 @@ class Album constructor(raw: Raw, override val songs: List) : MusicParent( val durationMs = songs.sumOf { it.durationMs } private var _artist: Artist? = null + /** The parent artist of this album. */ val artist: Artist get() = unlikelyToBeNull(_artist) @@ -634,9 +640,8 @@ class Date private constructor(private val tokens: List) : Comparable fun from(timestamp: String): Date? { val groups = (ISO8601_REGEX.matchEntire(timestamp) ?: return null) - .groupValues.mapIndexedNotNull { index, s -> - if (index % 2 != 0) s.toIntOrNull() else null - } + .groupValues + .mapIndexedNotNull { index, s -> if (index % 2 != 0) s.toIntOrNull() else null } return fromTokens(groups) } 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 b3650ac0d..3e4ad40d1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -20,6 +20,8 @@ package org.oxycblt.auxio.music import android.content.Context import android.net.Uri import android.provider.OpenableColumns +import org.oxycblt.auxio.music.MusicStore.Callback +import org.oxycblt.auxio.music.MusicStore.Library import org.oxycblt.auxio.util.contentResolverSafe /** @@ -99,12 +101,16 @@ class MusicStore private constructor() { /** Sanitize an old item to find the corresponding item in a new library. */ fun sanitize(song: Song) = find(song.uid) + /** Sanitize an old item to find the corresponding item in a new library. */ fun sanitize(songs: List) = songs.mapNotNull { sanitize(it) } + /** Sanitize an old item to find the corresponding item in a new library. */ fun sanitize(album: Album) = find(album.uid) + /** Sanitize an old item to find the corresponding item in a new library. */ fun sanitize(artist: Artist) = find(artist.uid) + /** Sanitize an old item to find the corresponding item in a new library. */ fun sanitize(genre: Genre) = find(genre.uid) diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt index 42fedbdf2..0ac219856 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt @@ -24,9 +24,9 @@ import android.database.Cursor import android.net.Uri import android.provider.MediaStore import android.text.format.DateUtils +import java.util.UUID import org.oxycblt.auxio.R import org.oxycblt.auxio.util.logD -import java.util.UUID /** Shortcut for making a [ContentResolver] query with less superfluous arguments. */ fun ContentResolver.queryCursor( @@ -59,13 +59,17 @@ val Long.audioUri: Uri val Long.albumCoverUri: Uri get() = ContentUris.withAppendedId(EXTERNAL_ALBUM_ART_URI, this) - /** Shortcut to resolve a year from a nullable date. Will return "No Date" if it is null. */ fun Date?.resolveYear(context: Context) = this?.resolveYear(context) ?: context.getString(R.string.def_date) /** Converts this string to a UUID, or returns null if it is not valid. */ -fun String.toUuid() = try { UUID.fromString(this) } catch (e: IllegalArgumentException) { null } +fun String.toUuid() = + try { + UUID.fromString(this) + } catch (e: IllegalArgumentException) { + null + } /** Converts a long in milliseconds to a long in deci-seconds */ fun Long.msToDs() = floorDiv(100) @@ -115,4 +119,4 @@ fun Long.formatDurationSecs(isElapsed: Boolean): String { } return durationString -} \ No newline at end of file +} 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 ffdc38736..a226cbf9c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt @@ -30,10 +30,12 @@ class MusicViewModel : ViewModel(), Indexer.Callback { private val indexer = Indexer.getInstance() private val _indexerState = MutableStateFlow(null) + /** The current music indexing state. */ val indexerState: StateFlow = _indexerState private val _libraryExists = MutableStateFlow(false) + /** Whether a music library has successfully been loaded. */ val libraryExists: StateFlow = _libraryExists diff --git a/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt index 6b6d4d6a7..f3673440c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt @@ -31,7 +31,6 @@ 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) @@ -48,9 +47,9 @@ class Directory private constructor(val volume: StorageVolume, val relativePath: // "primary" actually corresponds to the internal storage, not the primary volume. // Removable storage is represented with the UUID. if (volume.isInternalCompat) { - "${DOCUMENT_URI_PRIMARY_NAME}:${relativePath}" + "$DOCUMENT_URI_PRIMARY_NAME:$relativePath" } else { - volume.uuidCompat?.let { uuid -> "${uuid}:${relativePath}" } + volume.uuidCompat?.let { uuid -> "$uuid:$relativePath" } } override fun hashCode(): Int { diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheLayer.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheLayer.kt index 893fc14a1..5e4efde73 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheLayer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheLayer.kt @@ -1,10 +1,25 @@ +/* + * 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.music.extractor import org.oxycblt.auxio.music.Song -/** - * TODO: Stub class, not implemented yet - */ +/** TODO: Stub class, not implemented yet */ class CacheLayer { fun init() { // STUB: Add cache database @@ -15,4 +30,4 @@ class CacheLayer { } fun maybePopulateCachedRaw(raw: Song.Raw) = false -} \ No newline at end of file +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreLayer.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreLayer.kt index cb666c8e0..96e7d3cdb 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreLayer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreLayer.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.music.extractor import android.content.Context @@ -9,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.Date import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.music.Song @@ -20,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 @@ -81,13 +98,13 @@ import java.io.File */ /** - * The layer that loads music from the MediaStore database. This is an intermediate step in - * the music loading process. + * The layer that loads music from the MediaStore database. This is an intermediate step in the + * music loading process. * @author OxygenCobalt */ abstract class MediaStoreLayer(private val context: Context, private val cacheLayer: CacheLayer) { private var cursor: Cursor? = null - + private var idIndex = -1 private var titleIndex = -1 private var displayNameIndex = -1 @@ -101,18 +118,17 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa private var albumIdIndex = -1 private var artistIndex = -1 private var albumArtistIndex = -1 - + private val settings = Settings(context) private val _volumes = mutableListOf() - protected val volumes: List get() = _volumes + protected val volumes: List + get() = _volumes - /** - * Initialize this instance by making a query over the media database. - */ + /** Initialize this instance by making a query over the media database. */ open fun init(): Cursor { cacheLayer.init() - + val storageManager = context.getSystemServiceCompat(StorageManager::class) _volumes.addAll(storageManager.storageVolumesCompat) val dirs = settings.getMusicDirs(storageManager) @@ -149,39 +165,38 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa logD("Starting query [proj: ${projection.toList()}, selector: $selector, args: $args]") - val cursor = requireNotNull( - context.contentResolverSafe.queryCursor( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - projection, - selector, - args.toTypedArray())) { "Content resolver failure: No Cursor returned" } - .also { cursor = it } + val cursor = + requireNotNull( + 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) titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE) - displayNameIndex = - cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME) + displayNameIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME) mimeTypeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.MIME_TYPE) sizeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.SIZE) dateAddedIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATE_ADDED) - dateModifiedIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATE_MODIFIED) + dateModifiedIndex = + cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATE_MODIFIED) durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DURATION) yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR) albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM) albumIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM_ID) artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST) albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST) - + return cursor } - /** - * Finalize this instance by closing the cursor and finalizing the cache. - */ + /** Finalize this instance by closing the cursor and finalizing the cache. */ fun finalize(rawSongs: List) { cursor?.close() cursor = null - + cacheLayer.finalize(rawSongs) } @@ -281,7 +296,8 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa } // The album artist field is nullable and never has placeholder values. - raw.albumArtistNames = cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings) + raw.albumArtistNames = + cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings) } companion object { @@ -303,7 +319,6 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa } } - // Note: The separation between version-specific backends may not be the cleanest. To preserve // speed, we only want to add redundancy on known issues, not with possible issues. @@ -311,7 +326,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa * A [MediaStoreLayer] that completes the music loading process in a way compatible from * @author OxygenCobalt */ -class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : +class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : MediaStoreLayer(context, cacheLayer) { private var trackIndex = -1 private var dataIndex = -1 @@ -339,7 +354,7 @@ class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : override fun buildRaw(cursor: Cursor, raw: Song.Raw) { super.buildRaw(cursor, raw) - + // DATA is equivalent to the absolute path of the file. val data = cursor.getString(dataIndex) @@ -377,17 +392,18 @@ class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : * @author OxygenCobalt */ @RequiresApi(Build.VERSION_CODES.Q) -open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : MediaStoreLayer(context, cacheLayer) { +open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : + MediaStoreLayer(context, cacheLayer) { private var volumeIndex = -1 private var relativePathIndex = -1 override fun init(): Cursor { val cursor = super.init() - + volumeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.VOLUME_NAME) relativePathIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.RELATIVE_PATH) - + return cursor } @@ -431,7 +447,8 @@ open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : * @author OxygenCobalt */ @RequiresApi(Build.VERSION_CODES.Q) -open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : BaseApi29MediaStoreLayer(context, cacheLayer) { +open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : + BaseApi29MediaStoreLayer(context, cacheLayer) { private var trackIndex = -1 override fun init(): Cursor { @@ -445,7 +462,7 @@ open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : Base override fun buildRaw(cursor: Cursor, raw: Song.Raw) { super.buildRaw(cursor, raw) - + // This backend is volume-aware, but does not support the modern track fields. // Use the old field instead. val rawTrack = cursor.getIntOrNull(trackIndex) @@ -462,7 +479,8 @@ open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : Base * @author OxygenCobalt */ @RequiresApi(Build.VERSION_CODES.R) -class Api30MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : BaseApi29MediaStoreLayer(context, cacheLayer) { +class Api30MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : + BaseApi29MediaStoreLayer(context, cacheLayer) { private var trackIndex: Int = -1 private var discIndex: Int = -1 diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataLayer.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataLayer.kt index 58b4180e3..ac4244415 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataLayer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataLayer.kt @@ -1,20 +1,36 @@ +/* + * 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.music.extractor import android.content.Context import androidx.core.text.isDigitsOnly import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MetadataRetriever +import com.google.android.exoplayer2.metadata.Metadata import com.google.android.exoplayer2.metadata.id3.TextInformationFrame import com.google.android.exoplayer2.metadata.vorbis.VorbisComment +import org.oxycblt.auxio.music.Date import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.audioUri import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.logD -import com.google.android.exoplayer2.metadata.Metadata -import org.oxycblt.auxio.music.Date import org.oxycblt.auxio.util.logW - /** * The layer that leverages ExoPlayer's metadata retrieval system to index metadata. * @@ -32,14 +48,10 @@ class MetadataLayer(private val context: Context, private val mediaStoreLayer: M private val settings = Settings(context) private val taskPool: Array = arrayOfNulls(TASK_CAPACITY) - /** - * Initialize the sub-layers that this layer relies on. - */ + /** Initialize the sub-layers that this layer relies on. */ fun init() = mediaStoreLayer.init().count - /** - * Finalize the sub-layers that this layer relies on. - */ + /** Finalize the sub-layers that this layer relies on. */ fun finalize(rawSongs: List) = mediaStoreLayer.finalize(rawSongs) fun parse(emit: (Song.Raw) -> Unit) { @@ -90,7 +102,6 @@ class MetadataLayer(private val context: Context, private val mediaStoreLayer: M } } - companion object { /** The amount of tasks this backend can run efficiently at once. */ private const val TASK_CAPACITY = 8 @@ -191,7 +202,7 @@ class Task(context: Context, private val settings: Settings, private val raw: So tags["TRCK"]?.run { get(0).parsePositionNum() }?.let { raw.track = it } // Disc, as NN/TT - tags["TPOS"]?.run { get(0).parsePositionNum() } ?.let { raw.disc = it } + tags["TPOS"]?.run { get(0).parsePositionNum() }?.let { raw.disc = it } // Dates are somewhat complicated, as not only did their semantics change from a flat year // value in ID3v2.3 to a full ISO-8601 date in ID3v2.4, but there are also a variety of @@ -203,9 +214,8 @@ class Task(context: Context, private val settings: Settings, private val raw: So // 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["TDRC"]?.run { get(0).parseTimestamp() } - ?: tags["TDRL"]?.run { get(0).parseTimestamp() } - ?: parseId3v23Date(tags)) + ?: tags["TDRC"]?.run { get(0).parseTimestamp() } + ?: tags["TDRL"]?.run { get(0).parseTimestamp() } ?: parseId3v23Date(tags)) ?.let { raw.date = it } // (Sort) Album @@ -230,7 +240,9 @@ class Task(context: Context, private val settings: Settings, private val raw: So } private fun parseId3v23Date(tags: Map>): Date? { - val year = tags["TORY"]?.run { get(0).toIntOrNull() } ?: tags["TYER"]?.run { get(0).toIntOrNull() } ?: return null + val year = + tags["TORY"]?.run { get(0).toIntOrNull() } + ?: tags["TYER"]?.run { get(0).toIntOrNull() } ?: return null val tdat = tags["TDAT"] return if (tdat != null && tdat[0].length == 4 && tdat[0].isDigitsOnly()) { @@ -274,7 +286,7 @@ class Task(context: Context, private val settings: Settings, private val raw: So // (Sort) Album tags["ALBUM"]?.let { raw.albumName = it[0] } - tags["ALBUMSORT"]?.let { raw.albumSortName = it[0] } + tags["ALBUMSORT"]?.let { raw.albumSortName = it[0] } // (Sort) Artist tags["ARTIST"]?.let { raw.artistNames = it.parseMultiValue(settings) } 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 9f80693ff..349c38426 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 @@ -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.music.extractor import androidx.core.text.isDigitsOnly @@ -50,21 +67,23 @@ fun List.parseMultiValue(settings: Settings) = } /** - * Maybe a single tag into multi values with the user-preferred separators. If not enabled, - * the plain string will be returned. + * Maybe a single tag into multi values with the user-preferred separators. If not enabled, the + * plain string will be returned. */ fun String.maybeParseSeparators(settings: Settings): List { // Get the separators the user desires. If null, we don't parse any. val separators = settings.separators ?: return listOf(this) // Try to cache compiled regexes for particular separator combinations. - val regex = synchronized(SEPARATOR_REGEX_CACHE) { - SEPARATOR_REGEX_CACHE.getOrPut(separators) { Regex("[^\\\\][$separators]") } - } + val regex = + synchronized(SEPARATOR_REGEX_CACHE) { + SEPARATOR_REGEX_CACHE.getOrPut(separators) { Regex("[^\\\\][$separators]") } + } - val escape = synchronized(ESCAPE_REGEX_CACHE) { - ESCAPE_REGEX_CACHE.getOrPut(separators) { Regex("\\\\[$separators]")} - } + val escape = + synchronized(ESCAPE_REGEX_CACHE) { + ESCAPE_REGEX_CACHE.getOrPut(separators) { Regex("\\\\[$separators]") } + } return regex.split(this).map { value -> // Convert escaped separators to their correct value @@ -72,15 +91,13 @@ fun String.maybeParseSeparators(settings: Settings): List { } } -/** - * Parse a multi-value tag into a [ReleaseType], handling separators in the process. - */ +/** Parse a multi-value tag into a [ReleaseType], handling separators in the process. */ fun List.parseReleaseType(settings: Settings) = ReleaseType.parse(parseMultiValue(settings)) /** - * Parse a multi-value genre name using ID3v2 rules. If there is one value, the ID3v2.3 - * rules will be used, followed by separator parsing. Otherwise, each value will be iterated - * through, and numeric values transformed into string values. + * Parse a multi-value genre name using ID3v2 rules. If there is one value, the ID3v2.3 rules will + * be used, followed by separator parsing. Otherwise, each value will be iterated through, and + * numeric values transformed into string values. */ fun List.parseId3GenreNames(settings: Settings) = if (size == 1) { @@ -89,13 +106,9 @@ fun List.parseId3GenreNames(settings: Settings) = map { it.parseId3v1Genre() ?: it } } -/** - * Parse a single genre name using ID3v2.3 rules. - */ +/** Parse a single genre name using ID3v2.3 rules. */ fun String.parseId3GenreNames(settings: Settings) = - parseId3v1Genre()?.let { listOf(it) } ?: - parseId3v2Genre() ?: - maybeParseSeparators(settings) + parseId3v1Genre()?.let { listOf(it) } ?: parseId3v2Genre() ?: maybeParseSeparators(settings) private fun String.parseId3v1Genre(): String? = when { 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 f2f2381d6..eaa0dd00d 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 @@ -26,7 +26,11 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.oxycblt.auxio.BuildConfig -import org.oxycblt.auxio.music.* +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.MusicStore +import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.extractor.Api21MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api29MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api30MediaStoreLayer @@ -50,8 +54,8 @@ import org.oxycblt.auxio.util.logW * 3. Using the songs to build the library, which primarily involves linking up all data objects * with their corresponding parents/children. * - * This class in particular handles 3 primarily. For the code that handles 1 and 2, see the - * layer implementations. + * This class in particular handles 3 primarily. For the code that handles 1 and 2, see the layer + * implementations. * * This class also fulfills the role of maintaining the current music loading state, which seems * like a job for [MusicStore] but in practice is only really leveraged by the components that @@ -205,8 +209,10 @@ class Indexer { val mediaStoreLayer = when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Api30MediaStoreLayer(context, cacheLayer) - Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Api29MediaStoreLayer(context, cacheLayer) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> + Api30MediaStoreLayer(context, cacheLayer) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> + Api29MediaStoreLayer(context, cacheLayer) else -> Api21MediaStoreLayer(context, cacheLayer) } @@ -234,8 +240,8 @@ class Indexer { } /** - * Does the initial query over the song database using [metadataLayer]. The songs returned by this - * function are **not** well-formed. The companion [buildAlbums], [buildArtists], and + * Does the initial query over the song database using [metadataLayer]. 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. */ 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 ce0248749..6d74bc29b 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 @@ -20,7 +20,10 @@ package org.oxycblt.auxio.music.system import android.app.Service import android.content.Intent import android.database.ContentObserver -import android.os.* +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.os.PowerManager import android.provider.MediaStore import coil.imageLoader import kotlinx.coroutines.CoroutineScope @@ -218,7 +221,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { override fun onSettingChanged(key: String) { when (key) { getString(R.string.set_key_music_dirs), - getString(R.string.set_key_music_dirs_include)-> onStartIndexing() + getString(R.string.set_key_music_dirs_include) -> onStartIndexing() getString(R.string.set_key_observing) -> { if (!indexer.isIndexing) { updateIdleSession() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/ForcedLTRFrameLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/ForcedLTRFrameLayout.kt index ac15dc1d1..4cc33e656 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/ForcedLTRFrameLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/ForcedLTRFrameLayout.kt @@ -41,7 +41,7 @@ constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, - defStyleRes: Int = 0, + defStyleRes: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { override fun onFinishInflate() { super.onFinishInflate() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt index b382e5d6b..14697035e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt @@ -24,6 +24,7 @@ import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.msToDs import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.MainNavigationAction @@ -33,7 +34,6 @@ import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getColorCompat -import org.oxycblt.auxio.music.msToDs /** * A fragment showing the current playback state in a compact manner. Used as the bar for the diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt index 6f803acd5..40d0ca545 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt @@ -26,10 +26,13 @@ import org.oxycblt.auxio.IntegerTable enum class PlaybackMode { /** Construct the queue from the genre's songs */ ALL_SONGS, + /** Construct the queue from the artist's songs */ IN_ALBUM, + /** Construct the queue from the album's songs */ IN_ARTIST, + /** Construct the queue from all songs */ IN_GENRE; 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 06ec5d531..b12e42d24 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -31,11 +31,11 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.msToDs import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.util.collectImmediately -import org.oxycblt.auxio.music.msToDs import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.systemBarInsetsCompat 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 759e82855..70aeac6f5 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -30,15 +30,15 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.dsToMs +import org.oxycblt.auxio.music.msToDs import org.oxycblt.auxio.playback.state.InternalPlayer import org.oxycblt.auxio.playback.state.PlaybackStateDatabase import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.application -import org.oxycblt.auxio.music.dsToMs import org.oxycblt.auxio.util.logE -import org.oxycblt.auxio.music.msToDs /** * The ViewModel that provides a UI frontend for [PlaybackStateManager]. @@ -54,21 +54,25 @@ class PlaybackViewModel(application: Application) : private val playbackManager = PlaybackStateManager.getInstance() private val _song = MutableStateFlow(null) + /** The current song. */ val song: StateFlow get() = _song private val _parent = MutableStateFlow(null) + /** The current model that is being played from, such as an [Album] or [Artist] */ val parent: StateFlow = _parent private val _isPlaying = MutableStateFlow(false) val isPlaying: StateFlow get() = _isPlaying private val _positionDs = MutableStateFlow(0L) + /** The current playback position, in *deci-seconds* */ val positionDs: StateFlow get() = _positionDs private val _repeatMode = MutableStateFlow(RepeatMode.NONE) + /** The current repeat mode, see [RepeatMode] for more information */ val repeatMode: StateFlow get() = _repeatMode diff --git a/app/src/main/java/org/oxycblt/auxio/playback/StyledSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/StyledSeekBar.kt index 98f599de4..28334287c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/StyledSeekBar.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/StyledSeekBar.kt @@ -43,11 +43,7 @@ import org.oxycblt.auxio.util.logD */ class StyledSeekBar @JvmOverloads -constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, -) : +constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ForcedLTRFrameLayout(context, attrs, defStyleAttr), Slider.OnSliderTouchListener, Slider.OnChangeListener { 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 0a5933705..2d0483aad 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 @@ -28,8 +28,13 @@ import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemQueueSongBinding import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.ui.recycler.* -import org.oxycblt.auxio.util.* +import org.oxycblt.auxio.ui.recycler.IndicatorAdapter +import org.oxycblt.auxio.ui.recycler.SongViewHolder +import org.oxycblt.auxio.ui.recycler.SyncListDiffer +import org.oxycblt.auxio.util.context +import org.oxycblt.auxio.util.getAttrColorCompat +import org.oxycblt.auxio.util.getDimen +import org.oxycblt.auxio.util.inflater class QueueAdapter(private val listener: QueueItemListener) : RecyclerView.Adapter() { @@ -104,10 +109,8 @@ interface QueueItemListener { fun onPickUp(viewHolder: RecyclerView.ViewHolder) } -class QueueSongViewHolder -private constructor( - private val binding: ItemQueueSongBinding, -) : IndicatorAdapter.ViewHolder(binding.root) { +class QueueSongViewHolder private constructor(private val binding: ItemQueueSongBinding) : + IndicatorAdapter.ViewHolder(binding.root) { val bodyView: View get() = binding.body val backgroundView: View 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 9586ba7da..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 @@ -25,7 +25,11 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.R import org.oxycblt.auxio.ui.AuxioSheetBehavior -import org.oxycblt.auxio.util.* +import org.oxycblt.auxio.util.getAttrColorCompat +import org.oxycblt.auxio.util.getDimen +import org.oxycblt.auxio.util.getDimenSize +import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat +import org.oxycblt.auxio.util.systemBarInsetsCompat /** * The bottom sheet behavior designed for the queue in particular. diff --git a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGain.kt b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGain.kt index 06ea2ce0c..df8a1694e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGain.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGain.kt @@ -23,8 +23,10 @@ import org.oxycblt.auxio.IntegerTable enum class ReplayGainMode { /** Apply the track gain, falling back to the album gain if the track gain is not found. */ TRACK, + /** Apply the album gain, falling back to the track gain if the album gain is not found. */ ALBUM, + /** Apply the album gain only when playing from an album, defaulting to track gain otherwise. */ DYNAMIC; @@ -46,5 +48,5 @@ data class ReplayGainPreAmp( /** The value to use when ReplayGain tags are present. */ val with: Float, /** The value to use when ReplayGain tags are not present. */ - val without: Float, + val without: Float ) 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 274bea984..02461b077 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 @@ -116,8 +116,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? { @@ -258,7 +257,7 @@ class PlaybackStateDatabase private constructor(context: Context) : val parent: MusicParent?, val positionMs: Long, val repeatMode: RepeatMode, - val isShuffled: Boolean, + val isShuffled: Boolean ) private data class RawState( 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 a3e8284b0..36fe9f03f 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 @@ -27,6 +27,7 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.playback.state.PlaybackStateManager.Callback import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW @@ -58,13 +59,16 @@ class PlaybackStateManager private constructor() { /** The currently playing song. Null if there isn't one */ val song get() = queue.getOrNull(index) + /** The parent the queue is based on, null if all songs */ var parent: MusicParent? = null private set private var _queue = mutableListOf() + /** The current queue determined by [parent] */ val queue get() = _queue + /** The current position in the queue */ var index = -1 private set @@ -79,6 +83,7 @@ class PlaybackStateManager private constructor() { field = value notifyRepeatModeChanged() } + /** Whether the queue is shuffled */ var isShuffled = false private set 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 198662228..263f27140 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -53,6 +53,7 @@ class SearchViewModel(application: Application) : private val settings = Settings(application) private val _searchResults = MutableStateFlow(listOf()) + /** Current search results from the last [search] call. */ val searchResults: StateFlow> get() = _searchResults 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 c196f91c8..2fc7ef888 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -36,10 +36,10 @@ import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.ui.fragment.ViewBindingFragment import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.collectImmediately -import org.oxycblt.auxio.music.formatDurationMs import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.systemBarInsetsCompat 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 1749e56f4..188e0e88f 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -19,7 +19,9 @@ package org.oxycblt.auxio.settings import android.content.Context import android.content.SharedPreferences +import android.os.Build import android.os.storage.StorageManager +import android.util.Log import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.edit import androidx.preference.PreferenceManager @@ -34,7 +36,6 @@ import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.accent.Accent -import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -215,13 +216,12 @@ class Settings(private val context: Context, private val callback: Callback? = n } } - /** - * The list of separators the user wants to parse by. - */ + /** The list of separators the user wants to parse by. */ var separators: String? // Differ from convention and store a string of separator characters instead of an int // code. This makes it easier to use in Regexes and makes it more extendable. - get() = inner.getString(context.getString(R.string.set_key_separators), null)?.ifEmpty { null } + get() = + inner.getString(context.getString(R.string.set_key_separators), null)?.ifEmpty { null } set(value) { inner.edit { putString(context.getString(R.string.set_key_separators), value) @@ -344,3 +344,34 @@ class Settings(private val context: Context, private val callback: Callback? = n } } } + +// --- COMPAT --- + +fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent { + val currentKey = context.getString(R.string.set_key_accent) + + if (prefs.contains(OldKeys.KEY_ACCENT3)) { + Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT3}") + + var accent = prefs.getInt(OldKeys.KEY_ACCENT3, 5) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // Accents were previously frozen as soon as the OS was updated to android twelve, + // as dynamic colors were enabled by default. This is no longer the case, so we need + // to re-update the setting to dynamic colors here. + accent = 16 + } + + prefs.edit { + putInt(currentKey, accent) + remove(OldKeys.KEY_ACCENT3) + apply() + } + } + + return Accent.from(prefs.getInt(currentKey, Accent.DEFAULT)) +} + +/** Cache of the old keys used in Auxio. */ +private object OldKeys { + const val KEY_ACCENT3 = "auxio_accent" +} diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt deleted file mode 100644 index c12090967..000000000 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2021 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.settings - -import android.content.Context -import android.content.SharedPreferences -import android.os.Build -import android.util.Log -import androidx.core.content.edit -import org.oxycblt.auxio.R -import org.oxycblt.auxio.ui.accent.Accent - -// A couple of utils for migrating from old settings values to the new formats. -// Usually, these will last for 6 months before being removed. - -fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent { - val currentKey = context.getString(R.string.set_key_accent) - - if (prefs.contains(OldKeys.KEY_ACCENT3)) { - Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT3}") - - var accent = prefs.getInt(OldKeys.KEY_ACCENT3, 5) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // Accents were previously frozen as soon as the OS was updated to android twelve, - // as dynamic colors were enabled by default. This is no longer the case, so we need - // to re-update the setting to dynamic colors here. - accent = 16 - } - - prefs.edit { - putInt(currentKey, accent) - remove(OldKeys.KEY_ACCENT3) - apply() - } - } - - return Accent.from(prefs.getInt(currentKey, Accent.DEFAULT)) -} - -/** Cache of the old keys used in Auxio. */ -private object OldKeys { - const val KEY_ACCENT3 = "auxio_accent" -} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt b/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt index b730c5a8f..068c8e1c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/AuxioSheetBehavior.kt @@ -26,7 +26,8 @@ import android.view.WindowInsets import androidx.coordinatorlayout.widget.CoordinatorLayout import com.google.android.material.bottomsheet.NeoBottomSheetBehavior import org.oxycblt.auxio.R -import org.oxycblt.auxio.util.* +import org.oxycblt.auxio.util.getDimen +import org.oxycblt.auxio.util.systemGestureInsetsCompat /** * Implements a reasonable enough skeleton around BottomSheetBehavior (Excluding auxio extensions in diff --git a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt b/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt index 23585dda3..cf1fd6ab0 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt @@ -30,11 +30,13 @@ import org.oxycblt.auxio.util.logD */ class NavigationViewModel : ViewModel() { private val _mainNavigationAction = MutableStateFlow(null) + /** Flag for main fragment navigation. Intended for MainFragment use only. */ val mainNavigationAction: StateFlow get() = _mainNavigationAction private val _exploreNavigationItem = MutableStateFlow(null) + /** * Flag for navigation within the explore fragments. Observe this to coordinate navigation to an * item's UI. @@ -85,12 +87,16 @@ class NavigationViewModel : ViewModel() { sealed class MainNavigationAction { /** Expand the playback panel. */ object Expand : MainNavigationAction() + /** Collapse the playback panel. */ object Collapse : MainNavigationAction() + /** Go to settings. */ object Settings : MainNavigationAction() + /** Go to the about page. */ object About : MainNavigationAction() + /** Show song details. */ data class SongDetails(val song: Song) : MainNavigationAction() } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt index 892d34c93..0e9c8ac84 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt @@ -18,7 +18,6 @@ package org.oxycblt.auxio.ui import androidx.annotation.IdRes -import kotlin.UnsupportedOperationException import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R import org.oxycblt.auxio.music.Album @@ -27,6 +26,7 @@ import org.oxycblt.auxio.music.Date import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.ui.Sort.Mode /** * Represents the sort modes used in Auxio. 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 3751989dc..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 @@ -349,7 +349,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr if (!dragging && thumbView.isUnder(downX, thumbView.top.toFloat(), minTouchTargetSize) && 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/recycler/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt index 8113da507..962e27e01 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 @@ -73,10 +73,8 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) : * The Shared ViewHolder for a [Album]. * @author OxygenCobalt */ -class AlbumViewHolder -private constructor( - private val binding: ItemParentBinding, -) : IndicatorAdapter.ViewHolder(binding.root) { +class AlbumViewHolder private constructor(private val binding: ItemParentBinding) : + IndicatorAdapter.ViewHolder(binding.root) { fun bind(item: Album, listener: MenuItemListener) { binding.parentImage.bind(item) @@ -157,10 +155,8 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin * The Shared ViewHolder for a [Genre]. * @author OxygenCobalt */ -class GenreViewHolder -private constructor( - private val binding: ItemParentBinding, -) : IndicatorAdapter.ViewHolder(binding.root) { +class GenreViewHolder private constructor(private val binding: ItemParentBinding) : + IndicatorAdapter.ViewHolder(binding.root) { fun bind(item: Genre, listener: MenuItemListener) { binding.parentImage.bind(item) 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 a8c165a16..66bf85e23 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt @@ -45,10 +45,9 @@ fun unlikelyToBeNull(value: T?) = /** Returns null if this value is 0. */ fun Int.nonZeroOrNull() = if (this > 0) this else null -/** Returns null if this value is not in [range]. */ +/** Returns null if this value is not in [range]. */ fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null - /** Lazily reflect to retrieve a [Field]. */ fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy { clazz.java.getDeclaredField(field).also { it.isAccessible = true } 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 601532157..84ac64139 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt @@ -44,6 +44,7 @@ fun createThinWidget(context: Context, state: WidgetComponent.WidgetState) = .applyRoundingToBackground(context) .applyMeta(context, state) .applyBasicControls(context, state) + /** * The small widget is for 2x2 widgets and just shows the cover art and playback controls. This is * generally because a Medium widget is too large for this widget size and a text-only widget is too 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 ef42a3af7..46163fbcd 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -160,6 +160,6 @@ class WidgetComponent(private val context: Context) : val cover: Bitmap?, val isPlaying: Boolean, val repeatMode: RepeatMode, - val isShuffled: Boolean, + val isShuffled: Boolean ) } diff --git a/build.gradle b/build.gradle index 4699d5121..3d4b68b2d 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.4.0-alpha10' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" - classpath "com.diffplug.spotless:spotless-plugin-gradle:6.6.1" + classpath "com.diffplug.spotless:spotless-plugin-gradle:6.10.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 10158 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 delta 9041 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8049c684f..ae04661ee 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c78733..a69d9cb6c 100755 --- a/gradlew +++ b/gradlew @@ -205,6 +205,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..53a6b238d 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal