diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2aaa3c6a4..f2afad7ce 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,7 +9,7 @@
> {
override suspend fun fetch(
@@ -30,8 +32,7 @@ class MosaicFetcher(private val context: Context) : Fetcher> {
): FetchResult {
val streams = mutableListOf()
- // Load the streams, the lower-quality MediaStore covers are used simply because using
- // the raw ones would make loading far too long. Its not that noticeable either.
+ // Load MediaStore streams
data.forEach {
val stream: InputStream? = context.contentResolver.openInputStream(it)
@@ -69,7 +70,9 @@ class MosaicFetcher(private val context: Context) : Fetcher> {
// For each stream, create a bitmap scaled to 1/4th of the mosaics combined size
// and place it on a corner of the canvas.
- for (stream in streams) {
+ streams.useForEach { stream ->
+ if (y == MOSAIC_BITMAP_SIZE) return@useForEach
+
val bitmap = Bitmap.createScaledBitmap(
BitmapFactory.decodeStream(stream),
MOSAIC_BITMAP_INCREMENT,
@@ -84,16 +87,9 @@ class MosaicFetcher(private val context: Context) : Fetcher> {
if (x == MOSAIC_BITMAP_SIZE) {
x = 0
y += MOSAIC_BITMAP_INCREMENT
-
- if (y == MOSAIC_BITMAP_SIZE) {
- break
- }
}
}
- // Close all the streams when done.
- streams.forEach { it.close() }
-
return DrawableResult(
drawable = finalBitmap.toDrawable(context.resources),
isSampled = false,
@@ -101,6 +97,16 @@ class MosaicFetcher(private val context: Context) : Fetcher> {
)
}
+ /**
+ * Iterate through a list of [Closeable]s, running [use] on each.
+ * @param action What to do for each [Closeable]
+ */
+ private fun List.useForEach(action: (T) -> R) {
+ forEach {
+ it.use(action)
+ }
+ }
+
override fun key(data: List): String = data.toString()
companion object {
diff --git a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt b/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt
index eb96510ee..d1f375fc8 100644
--- a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt
+++ b/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt
@@ -55,14 +55,14 @@ class PlaybackStateDatabase(context: Context) :
*/
private fun constructStateTable(command: StringBuilder): StringBuilder {
command.append("${PlaybackState.COLUMN_ID} LONG PRIMARY KEY,")
- command.append("${PlaybackState.COLUMN_SONG_NAME} STRING NOT NULL,")
- command.append("${PlaybackState.COLUMN_POSITION} LONG NOT NULL,")
- command.append("${PlaybackState.COLUMN_PARENT_NAME} STRING NOT NULL,")
- command.append("${PlaybackState.COLUMN_INDEX} INTEGER NOT NULL,")
- command.append("${PlaybackState.COLUMN_MODE} INTEGER NOT NULL,")
- command.append("${PlaybackState.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,")
- command.append("${PlaybackState.COLUMN_LOOP_MODE} INTEGER NOT NULL,")
- command.append("${PlaybackState.COLUMN_IN_USER_QUEUE} BOOLEAN NOT NULL)")
+ .append("${PlaybackState.COLUMN_SONG_NAME} STRING NOT NULL,")
+ .append("${PlaybackState.COLUMN_POSITION} LONG NOT NULL,")
+ .append("${PlaybackState.COLUMN_PARENT_NAME} STRING NOT NULL,")
+ .append("${PlaybackState.COLUMN_INDEX} INTEGER NOT NULL,")
+ .append("${PlaybackState.COLUMN_MODE} INTEGER NOT NULL,")
+ .append("${PlaybackState.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,")
+ .append("${PlaybackState.COLUMN_LOOP_MODE} INTEGER NOT NULL,")
+ .append("${PlaybackState.COLUMN_IN_USER_QUEUE} BOOLEAN NOT NULL)")
return command
}
@@ -72,9 +72,9 @@ class PlaybackStateDatabase(context: Context) :
*/
private fun constructQueueTable(command: StringBuilder): StringBuilder {
command.append("${QueueItem.COLUMN_ID} LONG PRIMARY KEY,")
- command.append("${QueueItem.COLUMN_SONG_NAME} LONG NOT NULL,")
- command.append("${QueueItem.COLUMN_ALBUM_NAME} LONG NOT NULL,")
- command.append("${QueueItem.COLUMN_IS_USER_QUEUE} BOOLEAN NOT NULL)")
+ .append("${QueueItem.COLUMN_SONG_NAME} LONG NOT NULL,")
+ .append("${QueueItem.COLUMN_ALBUM_NAME} LONG NOT NULL,")
+ .append("${QueueItem.COLUMN_IS_USER_QUEUE} BOOLEAN NOT NULL)")
return command
}
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 f0688a10d..13618d9d8 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
@@ -76,6 +76,7 @@ class AlbumDetailFragment : DetailFragment() {
detailModel.albumSortMode.observe(viewLifecycleOwner) { mode ->
logD("Updating sort mode to $mode")
+ // Detail header data is included
val data = mutableListOf(detailModel.currentAlbum.value!!).also {
it.addAll(mode.getSortedSongList(detailModel.currentAlbum.value!!.songs))
}
@@ -85,8 +86,9 @@ class AlbumDetailFragment : DetailFragment() {
detailModel.navToItem.observe(viewLifecycleOwner) {
if (it != null) {
- logD(it.name)
when (it) {
+ // Songs should be scrolled to if the album matches, or a new detail
+ // fragment should be launched otherwise.
is Song -> {
if (detailModel.currentAlbum.value!!.id == it.album.id) {
scrollToItem(it.id)
@@ -99,6 +101,8 @@ class AlbumDetailFragment : DetailFragment() {
}
}
+ // If the album matches, no need to do anything. Otherwise launch a new
+ // detail fragment.
is Album -> {
if (detailModel.currentAlbum.value!!.id == it.id) {
binding.detailRecycler.scrollToPosition(0)
@@ -110,8 +114,8 @@ class AlbumDetailFragment : DetailFragment() {
}
}
+ // Always launch a new ArtistDetailFragment.
is Artist -> {
- logD("Hello?")
findNavController().navigate(
AlbumDetailFragmentDirections.actionShowArtist(it.id)
)
@@ -125,7 +129,14 @@ class AlbumDetailFragment : DetailFragment() {
// --- PLAYBACKVIEWMODEL SETUP ---
playbackModel.song.observe(viewLifecycleOwner) {
- handlePlayingItem(detailAdapter)
+ if (playbackModel.mode.value == PlaybackMode.IN_ALBUM &&
+ playbackModel.parent.value?.id == detailModel.currentAlbum.value!!.id
+ ) {
+ detailAdapter.highlightSong(playbackModel.song.value, binding.detailRecycler)
+ } else {
+ // Clear the viewholders if the mode isn't ALL_SONGS
+ detailAdapter.highlightSong(null, binding.detailRecycler)
+ }
}
playbackModel.isInUserQueue.observe(viewLifecycleOwner) {
@@ -139,21 +150,6 @@ class AlbumDetailFragment : DetailFragment() {
return binding.root
}
- /**
- * Handle an update to the mode or the song and determine whether to highlight a song
- * item based off that
- */
- private fun handlePlayingItem(detailAdapter: AlbumDetailAdapter) {
- if (playbackModel.mode.value == PlaybackMode.IN_ALBUM &&
- playbackModel.parent.value?.id == detailModel.currentAlbum.value!!.id
- ) {
- detailAdapter.highlightSong(playbackModel.song.value, binding.detailRecycler)
- } else {
- // Clear the viewholders if the mode isn't ALL_SONGS
- detailAdapter.highlightSong(null, binding.detailRecycler)
- }
- }
-
private fun scrollToItem(id: Long) {
// Calculate where the item for the currently played song is
val pos = detailModel.albumSortMode.value!!.getSortedSongList(
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 8f927b42b..2911b58f0 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
@@ -11,6 +11,7 @@ import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
+import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode
@@ -70,6 +71,7 @@ class ArtistDetailFragment : DetailFragment() {
detailModel.artistSortMode.observe(viewLifecycleOwner) { mode ->
logD("Updating sort mode to $mode")
+ // Header detail data is always included
val data = mutableListOf(detailModel.currentArtist.value!!).also {
it.addAll(mode.getSortedAlbumList(detailModel.currentArtist.value!!.albums))
}
@@ -79,15 +81,18 @@ class ArtistDetailFragment : DetailFragment() {
detailModel.navToItem.observe(viewLifecycleOwner) {
if (it != null) {
+ // If the artist matches, no need to do anything, otherwise launch a new detail
if (it is Artist) {
if (it.id == detailModel.currentArtist.value!!.id) {
+ binding.detailRecycler.scrollToPosition(0)
detailModel.doneWithNavToItem()
} else {
findNavController().navigate(
ArtistDetailFragmentDirections.actionShowArtist(it.id)
)
}
- } else {
+ } else if (it !is Genre) {
+ // Determine the album id of the song or album, and then launch it otherwise
val albumId = if (it is Song) it.album.id else it.id
findNavController().navigate(
@@ -97,6 +102,7 @@ class ArtistDetailFragment : DetailFragment() {
}
}
+ // Highlight albums if they are being played
playbackModel.parent.observe(viewLifecycleOwner) { parent ->
if (playbackModel.mode.value == PlaybackMode.IN_ALBUM && parent is Album?) {
detailAdapter.setCurrentAlbum(parent, binding.detailRecycler)
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt
index d51d0caf9..408b4a261 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt
@@ -83,6 +83,7 @@ abstract class DetailFragment : Fragment() {
adapter = detailAdapter
setHasFixedSize(true)
+ // Set up a grid if the mode is landscape
if (isLandscape(resources)) {
layoutManager = GridLayoutManager(requireContext(), 2).also {
it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
@@ -93,6 +94,8 @@ abstract class DetailFragment : Fragment() {
}
}
+ // Since there is no elevation when the scroll position is zero, dont show
+ // the overscroll indicator.
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
overScrollMode = if (computeVerticalScrollOffset() == 0) {
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 bf0c17bbf..322edefe9 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
@@ -63,6 +63,7 @@ class GenreDetailFragment : DetailFragment() {
detailModel.genreSortMode.observe(viewLifecycleOwner) { mode ->
logD("Updating sort mode to $mode")
+ // Detail header data is included
val data = mutableListOf(detailModel.currentGenre.value!!).also {
it.addAll(mode.getSortedSongList(detailModel.currentGenre.value!!.songs))
}
@@ -73,6 +74,7 @@ class GenreDetailFragment : DetailFragment() {
detailModel.navToItem.observe(viewLifecycleOwner) {
if (it != null) {
when (it) {
+ // All items will launch new detail fragments.
is Artist -> findNavController().navigate(
GenreDetailFragmentDirections.actionShowArtist(it.id)
)
@@ -93,7 +95,14 @@ class GenreDetailFragment : DetailFragment() {
// --- PLAYBACKVIEWMODEL SETUP ---
playbackModel.song.observe(viewLifecycleOwner) {
- handlePlayingItem(detailAdapter)
+ if (playbackModel.mode.value == PlaybackMode.IN_GENRE &&
+ playbackModel.parent.value?.id == detailModel.currentGenre.value!!.id
+ ) {
+ detailAdapter.highlightSong(playbackModel.song.value, binding.detailRecycler)
+ } else {
+ // Clear the viewholders if the mode isn't ALL_SONGS
+ detailAdapter.highlightSong(null, binding.detailRecycler)
+ }
}
playbackModel.isInUserQueue.observe(viewLifecycleOwner) {
@@ -106,19 +115,4 @@ class GenreDetailFragment : DetailFragment() {
return binding.root
}
-
- /**
- * Handle an update to the mode or the song and determine whether to highlight a song
- * item based off that
- */
- private fun handlePlayingItem(detailAdapter: GenreDetailAdapter) {
- if (playbackModel.mode.value == PlaybackMode.IN_GENRE &&
- playbackModel.parent.value?.id == detailModel.currentGenre.value!!.id
- ) {
- detailAdapter.highlightSong(playbackModel.song.value, binding.detailRecycler)
- } else {
- // Clear the viewholders if the mode isn't ALL_SONGS
- detailAdapter.highlightSong(null, binding.detailRecycler)
- }
- }
}
diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt
index 20d4f5bc3..37f047a1d 100644
--- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt
@@ -101,11 +101,11 @@ class LibraryFragment : Fragment() {
/**
* Navigate to an item
- * @param baseModel The data things should be done with
+ * @param baseModel The item that should be navigated to.
*/
private fun onItemSelection(baseModel: BaseModel) {
if (baseModel is Song) {
- logE("onItemSelection does not support song")
+ logE("onItemSelection does not support songs")
return
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt
index 46a002a4a..6c108c85c 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt
@@ -46,7 +46,6 @@ class CompactPlaybackFragment : Fragment() {
// Put a placeholder song in the binding & hide the playback fragment initially.
binding.song = MusicStore.getInstance().songs[0]
binding.playbackModel = playbackModel
-
if (playbackModel.song.value == null && isLandscape) {
hideAll(binding)
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt
index c8a08ffd9..e18469e7a 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt
@@ -30,8 +30,8 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels()
private val binding: FragmentPlaybackBinding by memberBinding(FragmentPlaybackBinding::inflate) {
- // Marquee must be disabled on destroy to prevent memory leaks
- binding.playbackSong.isSelected = false
+ // Marquee must be disabled on destruction to prevent memory leaks
+ playbackSong.isSelected = false
}
// Colors
diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt
index ba6171cc2..14ca17aef 100644
--- a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt
@@ -56,7 +56,7 @@ class SearchAdapter(
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
- else -> error("Someone messed with the ViewHolder item types.")
+ else -> error("Invalid viewholder item type.")
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt
index 0fc21767f..682183de0 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt
@@ -63,9 +63,9 @@ class SettingsListFragment : PreferenceFragmentCompat() {
}
}
- private fun handlePreference(it: Preference) {
- it.apply {
- when (it.key) {
+ private fun handlePreference(pref: Preference) {
+ pref.apply {
+ when (key) {
SettingsManager.Keys.KEY_THEME -> {
setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt
index c326d7ae5..6fe9badf7 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt
@@ -94,7 +94,7 @@ fun Fragment.requireCompatActivity(): AppCompatActivity {
}
/**
- * "Render" a [Spanned] using [HtmlCompat].
+ * "Render" a [Spanned] using [HtmlCompat]. (As in making text bolded and whatnot).
* @return A [Spanned] that actually works.
*/
fun Spanned.render(): Spanned {
diff --git a/app/src/main/res/layout-land/fragment_compact_playback.xml b/app/src/main/res/layout-land/fragment_compact_playback.xml
index def69e666..6989d0c1b 100644
--- a/app/src/main/res/layout-land/fragment_compact_playback.xml
+++ b/app/src/main/res/layout-land/fragment_compact_playback.xml
@@ -44,9 +44,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_mid_small"
android:layout_marginEnd="@dimen/margin_mid_small"
- android:ellipsize="marquee"
android:fontFamily="@font/inter_semibold"
- android:marqueeRepeatLimit="marquee_forever"
+ android:ellipsize="end"
android:singleLine="true"
android:text="@{song.name}"
android:textAppearance="@style/TextAppearance.SmallHeader"
@@ -63,8 +62,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_mid_small"
android:layout_marginEnd="@dimen/margin_mid_small"
- android:ellipsize="marquee"
- android:marqueeRepeatLimit="marquee_forever"
+ android:ellipsize="end"
android:singleLine="true"
android:text="@{@string/format_info(song.album.artist.name, song.album.name)}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
diff --git a/app/src/main/res/layout/fragment_compact_playback.xml b/app/src/main/res/layout/fragment_compact_playback.xml
index db69a21e7..0fbee7dc8 100644
--- a/app/src/main/res/layout/fragment_compact_playback.xml
+++ b/app/src/main/res/layout/fragment_compact_playback.xml
@@ -53,9 +53,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_mid_small"
android:layout_marginEnd="@dimen/margin_mid_small"
- android:ellipsize="marquee"
+ android:ellipsize="end"
android:fontFamily="@font/inter_semibold"
- android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:text="@{song.name}"
android:textAppearance="@style/TextAppearance.SmallHeader"
@@ -72,8 +71,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_mid_small"
android:layout_marginEnd="@dimen/margin_mid_small"
- android:ellipsize="marquee"
- android:marqueeRepeatLimit="marquee_forever"
+ android:ellipsize="end"
android:singleLine="true"
android:text="@{@string/format_info(song.album.artist.name, song.album.name)}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
diff --git a/app/src/main/res/values/do_not_translate.xml b/app/src/main/res/values/do_not_translate.xml
index 2bdd848ee..969e537ba 100644
--- a/app/src/main/res/values/do_not_translate.xml
+++ b/app/src/main/res/values/do_not_translate.xml
@@ -1,10 +1,10 @@
- OxygenCobalt
+ OxygenCobalt
- %1$s / %2$s
- %1$s / %2$s / %3$s
- %1$s, %2$s
- <b>%1$s</b>: %2$s
+ %1$s / %2$s
+ %1$s / %2$s / %3$s
+ %1$s, %2$s
+ <b>%1$s</b>: %2$s
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2d9f97786..af9900758 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -93,7 +93,7 @@
When a playlist ends
Loop
Stop
- Loop & Pause
+ Loop and Pause
Remember shuffle
Keep shuffle on when playing a new song
@@ -108,10 +108,10 @@
State saved
- No music found.
- Music loading failed.
- Permissions to read storage are needed.
- Could not open link.
+ No music found
+ Music loading failed
+ Permissions to read storage are needed
+ Could not open link
Search your library…