From 245e0f5dc1a83884c2385a8745639cb1299c4e01 Mon Sep 17 00:00:00 2001 From: Clyde <34314096+cccClyde@users.noreply.github.com> Date: Mon, 7 Feb 2022 12:26:22 +0800 Subject: [PATCH 01/38] Update strings.xml Update zh-rCN translations accordingly with the latest `strings.xml`. --- app/src/main/res/values-zh-rCN/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8ccb2373e..a1eabcaa2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -103,6 +103,8 @@ 内容 保存播放状态 立即保存当前播放状态 + 重新加载音乐 + 将会重启应用 排除文件夹 被排除文件夹的内容将从媒体库中隐藏 @@ -143,6 +145,7 @@ 未知艺术家 未知流派 没有日期 + 无曲目编号 未播放音乐 歌曲名称 艺术家姓名 @@ -169,10 +172,12 @@ 已加载 %d 首曲目 + %d 首歌曲 "%d 首歌曲" + %d 张专辑 "%d 张专辑" From 4a326cc4ffc698c3fcf74c48cb7f50444c2920b1 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Mon, 7 Feb 2022 20:08:18 -0700 Subject: [PATCH 02/38] all: fix sloppy code Fix some dumb and sloppy code that I made in the rush to complete 2.2.0. --- CHANGELOG.md | 8 +++++--- .../main/java/org/oxycblt/auxio/music/MusicLoader.kt | 8 ++++---- .../auxio/playback/state/PlaybackStateManager.kt | 11 +---------- app/src/main/res/values-ar-rIQ/strings.xml | 2 +- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 891991233..1b9a0b8dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog ## dev [v2.2.1 or v2.3.0] +- Updated chinese translations [courtesy of cccClyde] ## v2.2.0 #### What's New: -- Added arabic translations [courtesy of hasanpasha] -- Better russian translations [courtesy of lisiczka43] +- Added Arabic translations [Courtesy of hasanpasha] +- Improved Russian translations [Courtesy of lisiczka43] - Added option to reload the music library #### What's Improved: @@ -18,9 +19,10 @@ artist they are grouped up in #### What's Fixed: - Fixed crash on some devices configured to use French or Czech translations -- Malformed indicies should now be corrected when the playback state is restored +- Malformed indices should now be corrected when the playback state is restored - Fixed issue where track numbers would not be shown in the native language's numeric format - Fixed issue where the preference view would apply the M3 switches inconsistently +- Fixed issue where the now playing indicator on the playback screen would use an internal name #### Dev/Meta: - Removed 1.4.X compat diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index d978a4a71..f6a5cbdc6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -72,7 +72,6 @@ import java.lang.Exception * * @author OxygenCobalt */ -@Suppress("InlinedApi") class MusicLoader { data class Library( val genres: List, @@ -107,6 +106,7 @@ class MusicLoader { ) } + @Suppress("InlinedApi") private fun loadSongs(context: Context): List { var songs = mutableListOf() val blacklistDatabase = ExcludedDatabase.getInstance(context) @@ -285,7 +285,7 @@ class MusicLoader { private fun readGenres(context: Context, songs: List): List { val genres = mutableListOf() - val genreCursor = context.contentResolver.query( + val genreCursor = context.applicationContext.contentResolver.query( MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, arrayOf( MediaStore.Audio.Genres._ID, @@ -338,7 +338,7 @@ class MusicLoader { val genreSongs = mutableListOf() // Don't even bother blacklisting here as useless iterations are less expensive than IO - val songCursor = context.contentResolver.query( + val songCursor = context.applicationContext.contentResolver.query( MediaStore.Audio.Genres.Members.getContentUri("external", genreId), arrayOf(MediaStore.Audio.Genres.Members._ID), null, null, null @@ -356,7 +356,7 @@ class MusicLoader { } } - // Some genres might be empty due to MediaStore empty. + // Some genres might be empty due to MediaStore insanity. // If that is the case, we drop them. return genreSongs.ifEmpty { null } } 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 0fe3b6348..171fa7cb1 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 @@ -519,8 +519,6 @@ class PlaybackStateManager private constructor() { val database = PlaybackStateDatabase.getInstance(context) - logD("$mPlaybackMode") - database.writeState( PlaybackStateDatabase.SavedState( mSong, mPosition, mParent, mIndex, @@ -595,14 +593,6 @@ class PlaybackStateManager private constructor() { private fun unpackQueue(queue: MutableList) { mQueue = queue - - // Sanity check: Ensure that the - mSong?.let { song -> - while (mQueue.getOrNull(mIndex) != song) { - mIndex-- - } - } - pushQueueUpdate() } @@ -633,6 +623,7 @@ class PlaybackStateManager private constructor() { if (correctedIndex > -1) { logD("Correcting malformed index to $correctedIndex") mIndex = correctedIndex + pushQueueUpdate() } } } diff --git a/app/src/main/res/values-ar-rIQ/strings.xml b/app/src/main/res/values-ar-rIQ/strings.xml index 475457b40..bff4fce1b 100644 --- a/app/src/main/res/values-ar-rIQ/strings.xml +++ b/app/src/main/res/values-ar-rIQ/strings.xml @@ -177,7 +177,7 @@ - ألبومات d% + %d ألبومات %d البوم %d ألبومات %d ألبومات From f4217a337a2ee05151ef047776d8f892dd04612d Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 8 Feb 2022 06:23:43 -0700 Subject: [PATCH 03/38] style: tweak body typography Apply body typography in new places in the app. For awhile, Body and Title typography were used interchangeably, as they occupy the same text size range. This commit defines the Body text as to be used for one-line widgets or tertiary widgets, while the Title text is defined to be used for multi-line or heading widgets. --- app/src/main/res/layout/fragment_search.xml | 3 +- app/src/main/res/layout/item_album_song.xml | 2 +- app/src/main/res/layout/item_artist_song.xml | 2 +- app/src/main/res/layout/item_excluded_dir.xml | 1 + app/src/main/res/layout/item_genre_song.xml | 2 +- app/src/main/res/values/styles_ui.xml | 5 ++ app/src/main/res/values/typography.xml | 53 +++++++++++++------ 7 files changed, 47 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 716fe376d..4cf5d42cc 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -40,8 +40,7 @@ android:imeOptions="actionSearch|flagNoExtractUi" android:inputType="textFilter" android:paddingStart="0dp" - android:paddingEnd="0dp" - android:textAppearance="@style/TextAppearance.Auxio.TitleMedium" /> + android:paddingEnd="0dp" /> diff --git a/app/src/main/res/layout/item_album_song.xml b/app/src/main/res/layout/item_album_song.xml index 5be733658..7dd52f238 100644 --- a/app/src/main/res/layout/item_album_song.xml +++ b/app/src/main/res/layout/item_album_song.xml @@ -38,7 +38,7 @@ android:minWidth="@dimen/size_track_number" android:text="@{@string/fmt_track(song.track)}" android:textAlignment="center" - android:textAppearance="@style/TextAppearance.Auxio.TitleMedium" + android:textAppearance="@style/TextAppearance.Auxio.BodyLarge" android:textColor="@color/sel_accented_secondary" android:textSize="@dimen/text_size_ext_title_mid_larger" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/item_artist_song.xml b/app/src/main/res/layout/item_artist_song.xml index ec7a92ed6..a014af1bd 100644 --- a/app/src/main/res/layout/item_artist_song.xml +++ b/app/src/main/res/layout/item_artist_song.xml @@ -53,7 +53,7 @@ ?android:attr/textColorSecondary + + + + + + + + + - + + - - - - \ No newline at end of file From 04bec3161f1f2e1be42d8cd262ee2063bc931850 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 8 Feb 2022 06:53:53 -0700 Subject: [PATCH 04/38] music: modify model configuration Do a couple things to the music models: 1. Make the genre field non-nullable. This is because I beleive I've largely eliminated the genre bugs in previous versions and future ones can be caught with a crash screen I plan to add. 2. Make the initial album grouping process use hashCode instead of a pair of names. This just helps with loading speed in general, albeit I am slightly worried that it may result in improper grouping if some edge case appears. --- .../java/org/oxycblt/auxio/music/Models.kt | 48 ++++++++++++------- .../org/oxycblt/auxio/music/MusicLoader.kt | 28 +++++------ 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index 78fefe345..80f27daf6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -65,17 +65,17 @@ data class Song( /** The track number of this song. */ val track: Int, /** Internal field. Do not use. */ - val _mediaStoreId: Long, + val internalMediaStoreId: Long, /** Internal field. Do not use. */ - val _mediaStoreArtistName: String?, + val internalMediaStoreArtistName: String?, /** Internal field. Do not use. */ - val _mediaStoreAlbumArtistName: String?, + val internalMediaStoreAlbumArtistName: String?, /** Internal field. Do not use. */ - val _mediaStoreAlbumId: Long, + val internalMediaStoreAlbumId: Long, /** Internal field. Do not use. */ - val _mediaStoreAlbumName: String, + val internalMediaStoreAlbumName: String, /** Internal field. Do not use. */ - val _mediaStoreYear: Int + val internalMediaStoreYear: Int ) : Music() { override val id: Long get() { var result = name.hashCode().toLong() @@ -88,7 +88,7 @@ data class Song( /** The URI for this song. */ val uri: Uri get() = ContentUris.withAppendedId( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, _mediaStoreId + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, internalMediaStoreId ) /** The duration of this song, in seconds (rounded down) */ val seconds: Long get() = duration / 1000 @@ -100,8 +100,8 @@ data class Song( val album: Album get() = requireNotNull(mAlbum) var mGenre: Genre? = null - /** The genre of this song. May be null due to MediaStore insanity. */ - val genre: Genre? get() = mGenre + /** The genre of this song. Will be an "unknown genre" if the song does not have any. */ + val genre: Genre get() = requireNotNull(mGenre) /** An album name resolved to this song in particular. */ val resolvedAlbumName: String get() = @@ -109,15 +109,29 @@ data class Song( /** An artist name resolved to this song in particular. */ val resolvedArtistName: String get() = - _mediaStoreArtistName ?: album.artist.resolvedName + internalMediaStoreArtistName ?: album.artist.resolvedName + + /** Internal field. Do not use. */ + val internalGroupingId: Int get() { + var result = internalGroupingArtistName.lowercase().hashCode() + result = 31 * result + internalMediaStoreAlbumName.lowercase().hashCode() + return result + } + + /** Internal field. Do not use. */ + val internalGroupingArtistName: String get() = internalMediaStoreAlbumArtistName + ?: internalMediaStoreArtistName ?: MediaStore.UNKNOWN_STRING + + /** Internal field. Do not use. **/ + val internalMissingGenre: Boolean get() = mGenre == null /** Internal method. Do not use. */ - fun mediaStoreLinkAlbum(album: Album) { + fun internalLinkAlbum(album: Album) { mAlbum = album } /** Internal method. Do not use. */ - fun mediaStoreLinkGenre(genre: Genre) { + fun internalLinkGenre(genre: Genre) { mGenre = genre } } @@ -134,11 +148,11 @@ data class Album( /** The songs of this album. */ val songs: List, /** Internal field. Do not use. */ - val _mediaStoreArtistName: String, + val internalGroupingArtistName: String, ) : MusicParent() { init { for (song in songs) { - song.mediaStoreLinkAlbum(this) + song.internalLinkAlbum(this) } } @@ -165,7 +179,7 @@ data class Album( artist.resolvedName /** Internal method. Do not use. */ - fun mediaStoreLinkArtist(artist: Artist) { + fun internalLinkArtist(artist: Artist) { mArtist = artist } } @@ -182,7 +196,7 @@ data class Artist( ) : MusicParent() { init { for (album in albums) { - album.mediaStoreLinkArtist(this) + album.internalLinkArtist(this) } } @@ -202,7 +216,7 @@ data class Genre( ) : MusicParent() { init { for (song in songs) { - song.mediaStoreLinkGenre(this) + song.internalLinkGenre(this) } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index f6a5cbdc6..e6088f2d2 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -92,6 +92,7 @@ class MusicLoader { for (song in songs) { try { song.album.artist + song.genre } catch (e: Exception) { logE("Found malformed song: ${song.name}") throw e @@ -187,18 +188,13 @@ class MusicLoader { } songs = songs.distinctBy { - it.name to it._mediaStoreAlbumName to it._mediaStoreArtistName to it._mediaStoreAlbumArtistName to it.track to it.duration + it.name to it.internalMediaStoreAlbumName to it.internalMediaStoreArtistName to it.internalMediaStoreAlbumArtistName to it.track to it.duration }.toMutableList() return songs } private fun buildAlbums(songs: List): List { - // When assigning an artist to an album, use the album artist first, then the - // normal artist, and then the internal representation of an unknown artist name. - fun Song.resolveAlbumArtistName() = _mediaStoreAlbumArtistName ?: _mediaStoreArtistName - ?: MediaStore.UNKNOWN_STRING - // Group up songs by their lowercase artist and album name. This serves two purposes: // 1. Sometimes artist names can be styled differently, e.g "Rammstein" vs. "RAMMSTEIN". // This makes sure both of those are resolved into a single artist called "Rammstein" @@ -209,9 +205,7 @@ class MusicLoader { // the template, but it seems to work pretty well. val albums = mutableListOf() val songsByAlbum = songs.groupBy { song -> - val albumName = song._mediaStoreAlbumName - val artistName = song.resolveAlbumArtistName() - Pair(albumName.lowercase(), artistName.lowercase()) + song.internalGroupingId } for (entry in songsByAlbum) { @@ -220,14 +214,14 @@ class MusicLoader { // Use the song with the latest year as our metadata song. // This allows us to replicate the LAST_YEAR field, which is useful as it means that // weird years like "0" wont show up if there are alternatives. - val templateSong = requireNotNull(albumSongs.maxByOrNull { it._mediaStoreYear }) - val albumName = templateSong._mediaStoreAlbumName - val albumYear = templateSong._mediaStoreYear + val templateSong = requireNotNull(albumSongs.maxByOrNull { it.internalMediaStoreYear }) + val albumName = templateSong.internalMediaStoreAlbumName + val albumYear = templateSong.internalMediaStoreYear val albumCoverUri = ContentUris.withAppendedId( Uri.parse("content://media/external/audio/albumart"), - templateSong._mediaStoreAlbumId + templateSong.internalMediaStoreAlbumId ) - val artistName = templateSong.resolveAlbumArtistName() + val artistName = templateSong.internalGroupingArtistName albums.add( Album( @@ -245,7 +239,7 @@ class MusicLoader { private fun buildArtists(context: Context, albums: List): List { val artists = mutableListOf() - val albumsByArtist = albums.groupBy { it._mediaStoreArtistName } + val albumsByArtist = albums.groupBy { it.internalGroupingArtistName } for (entry in albumsByArtist) { val artistName = entry.key @@ -318,7 +312,7 @@ class MusicLoader { } } - val songsWithoutGenres = songs.filter { it.genre == null } + val songsWithoutGenres = songs.filter { it.internalMissingGenre } if (songsWithoutGenres.isNotEmpty()) { // Songs that don't have a genre will be thrown into an unknown genre. @@ -350,7 +344,7 @@ class MusicLoader { while (cursor.moveToNext()) { val id = cursor.getLong(idIndex) - songs.find { it._mediaStoreId == id }?.let { song -> + songs.find { it.internalMediaStoreId == id }?.let { song -> genreSongs.add(song) } } From f377e144dd655b6636e5b71a0c48ce8d0c230b03 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Thu, 10 Feb 2022 17:34:03 -0700 Subject: [PATCH 05/38] style: remove elevation from toolbars Remove the elevation component from all toolbars and the bottom bar. Material3 states that top and bottom app bars should not cast a drop shadow. Auxio ignored this and used one anyway. This largely stemmed from incorrect use of the AppBarLayout styles, which were mostly just incorrect M2 styles with a new background plastered over. Fix this by creating a new style that inherits the proper M3 styles and then using that on all AppBarLayout instances in the app. --- .../main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt | 7 ++----- app/src/main/res/layout/fragment_about.xml | 4 +--- app/src/main/res/layout/fragment_detail.xml | 4 +--- app/src/main/res/layout/fragment_home.xml | 4 +--- app/src/main/res/layout/fragment_queue.xml | 4 +--- app/src/main/res/layout/fragment_search.xml | 4 +--- app/src/main/res/layout/fragment_settings.xml | 4 +--- app/src/main/res/values/styles_ui.xml | 5 +++++ 8 files changed, 13 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt index fb553cb8b..eefca4290 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt @@ -24,9 +24,8 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.util.getAttrColorSafe -import org.oxycblt.auxio.util.getDimenSafe +import org.oxycblt.auxio.util.getDimenSizeSafe import org.oxycblt.auxio.util.getDrawableSafe -import org.oxycblt.auxio.util.pxOfDp import org.oxycblt.auxio.util.replaceInsetsCompat import org.oxycblt.auxio.util.stateList import org.oxycblt.auxio.util.systemBarInsetsCompat @@ -98,7 +97,6 @@ class PlaybackLayout @JvmOverloads constructor( private var initMotionX = 0f private var initMotionY = 0f private val tRect = Rect() - private val elevationNormal = context.getDimenSafe(R.dimen.elevation_normal) /** See [isDragging] */ private val dragStateField = ViewDragHelper::class.java.getDeclaredField("mDragState").apply { @@ -119,7 +117,7 @@ class PlaybackLayout @JvmOverloads constructor( playbackContainerBg = MaterialShapeDrawable.createWithElevationOverlay(context).apply { fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList - elevation = context.pxOfDp(elevationNormal).toFloat() + elevation = context.getDimenSizeSafe(R.dimen.elevation_normal).toFloat() } // The way we fade out the elevation overlay is not by actually reducing the elevation @@ -537,7 +535,6 @@ class PlaybackLayout @JvmOverloads constructor( // Slowly reduce the elevation of the container as we slide up, eventually resulting in a // neutral color instead of an elevated one when fully expanded. playbackContainerBg.alpha = (outRatio * 255).toInt() - playbackContainerView.translationZ = elevationNormal * outRatio // Fade out our bar view as we slide up playbackBarView.apply { diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 4ae3627f6..76cdfb2dd 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -12,9 +12,7 @@ diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 285774204..4bb06bb87 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -11,9 +11,7 @@ diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 4cf5d42cc..5108175dc 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -9,9 +9,7 @@ diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 89c367e0b..f2110e80b 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -13,9 +13,7 @@ diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index 7194f18c1..e738fd67a 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -2,6 +2,11 @@ + + @@ -40,16 +40,14 @@ diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index e738fd67a..c6042f9da 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -85,7 +85,7 @@ @@ -105,7 +105,7 @@ end 1 ?android:attr/textColorSecondary - @style/TextAppearance.Auxio.TitleMedium + @style/TextAppearance.Auxio.BodyLarge \ No newline at end of file From 30ad7f99db677c6947893c6e71e9a6e05d1b6738 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 13 Feb 2022 16:12:08 -0700 Subject: [PATCH 07/38] playback: fix headset focus bug Fix an issue where headset focus would restart playback unexpectedly. At some point during the broadcast refactor, I accidentally switched the values of CONNECTED and DISCONNECTED when handling AudioManager.ACTION_HEADSET_PLUG. This resulted in playback starting for no reason in some situations. --- CHANGELOG.md | 4 ++++ .../auxio/detail/recycler/ArtistDetailAdapter.kt | 2 +- .../java/org/oxycblt/auxio/music/MusicLoader.kt | 11 +++-------- .../org/oxycblt/auxio/playback/PlaybackFragment.kt | 1 + .../auxio/playback/state/PlaybackStateManager.kt | 14 ++------------ .../auxio/playback/system/PlaybackService.kt | 4 ++-- .../main/java/org/oxycblt/auxio/util/LogUtil.kt | 8 ++++---- .../main/java/org/oxycblt/auxio/widgets/Forms.kt | 8 ++++---- info/ARCHITECTURE.md | 7 ++++--- 9 files changed, 25 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42ee0b705..c080a4b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,12 @@ ## dev [v2.2.1 or v2.3.0] #### What's Improved - Updated chinese translations [courtesy of cccClyde] +- Use proper M3 top app bars - Use body typography in correct places +#### What's Fixed +- Fixed issue where playback would start unexpectedly when opening the app + ## v2.2.0 #### What's New: - Added Arabic translations [Courtesy of hasanpasha] 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 02b89073c..de8c0a32c 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 @@ -201,7 +201,7 @@ class ArtistDetailAdapter( // Get the genre that corresponds to the most songs in this artist, which would be // the most "Prominent" genre. binding.detailSubhead.text = data.songs - .groupBy { it.genre?.resolvedName } + .groupBy { it.genre.resolvedName } .entries.maxByOrNull { it.value.size } ?.key ?: context.getString(R.string.def_genre) diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index 058cc6d84..12f6dfb4a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -7,7 +7,6 @@ import android.provider.MediaStore import androidx.core.database.getStringOrNull import org.oxycblt.auxio.R import org.oxycblt.auxio.excluded.ExcludedDatabase -import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE import java.lang.Exception @@ -125,7 +124,7 @@ class MusicLoader { args += "$path%" // Append % so that the selector properly detects children } - context.contentResolver.query( + context.applicationContext.contentResolver.query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, arrayOf( MediaStore.Audio.AudioColumns._ID, @@ -179,8 +178,6 @@ class MusicLoader { substring(0 until lastIndexOfAny(listOf(fileName))) } - logD("SONG NAME: $title ALBUM: $album ARTIST: $artist ALBUM ARTIST: $albumArtist") - songs.add( Song( title, @@ -236,8 +233,6 @@ class MusicLoader { ) val artistName = templateSong.internalGroupingArtistName - logD("ALBUM NAME: $albumName PREFERRED ARTIST: $artistName") - albums.add( Album( albumName, @@ -294,7 +289,7 @@ class MusicLoader { private fun readGenres(context: Context, songs: List): List { val genres = mutableListOf() - val genreCursor = context.contentResolver.query( + val genreCursor = context.applicationContext.contentResolver.query( MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, arrayOf( MediaStore.Audio.Genres._ID, @@ -347,7 +342,7 @@ class MusicLoader { val genreSongs = mutableListOf() // Don't even bother blacklisting here as useless iterations are less expensive than IO - val songCursor = context.contentResolver.query( + val songCursor = context.applicationContext.contentResolver.query( MediaStore.Audio.Genres.Members.getContentUri("external", genreId), arrayOf(MediaStore.Audio.Genres.Members._ID), null, null, null 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 a67f375d3..26bfb16ef 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -127,6 +127,7 @@ class PlaybackFragment : Fragment() { } binding.playbackLoop.setImageResource(resId) + binding.playbackLoop.isActivated = loopMode != LoopMode.NONE } playbackModel.position.observe(viewLifecycleOwner) { pos -> 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 171fa7cb1..fea3fd451 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 @@ -151,17 +151,8 @@ class PlaybackStateManager private constructor() { } PlaybackMode.IN_GENRE -> { - val genre = song.genre - - // Don't do this if the genre is null - if (genre != null) { - mParent = genre - mQueue = genre.songs.toMutableList() - } else { - playSong(song, PlaybackMode.ALL_SONGS) - - return - } + mParent = song.genre + mQueue = song.genre.songs.toMutableList() } PlaybackMode.IN_ARTIST -> { @@ -463,7 +454,6 @@ class PlaybackStateManager private constructor() { */ fun seekTo(position: Long) { mPosition = position - callbacks.forEach { it.onSeek(position) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 15b76ed4f..78cbb9a2f 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -465,8 +465,8 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac AudioManager.ACTION_HEADSET_PLUG -> { when (intent.getIntExtra("state", -1)) { - 0 -> resumeFromPlug() - 1 -> pauseFromPlug() + 0 -> pauseFromPlug() + 1 -> resumeFromPlug() } } diff --git a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt index 1bc758b24..fc5bdb105 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt @@ -49,18 +49,18 @@ fun Any.logE(msg: String) { } /** - * Get a non-nullable name, used so that logs will always show up in the console. - * This also applies a special "Auxio" prefix so that messages can be filtered to just from the main codebase. + * Get a non-nullable name, used so that logs will always show up by Auxio * @return The name of the object, otherwise "Anonymous Object" */ private fun Any.getName(): String = "Auxio.${this::class.simpleName ?: "Anonymous Object"}" /** - * I know that this will not stop you, but consider what you are doing with your life, copiers. + * I know that this will not stop you, but consider what you are doing with your life, plagiarizers. * Do you want to live a fulfilling existence on this planet? Or do you want to spend your life * taking work others did and making it objectively worse so you could arbitrage a fraction of a * penny on every AdMob impression you get? You could do so many great things if you simply had - * the courage to come up with an idea of your own. Be better. + * the courage to come up with an idea of your own. If you still want to go on, I guess the only + * thing I can say is this: JUNE 1989 TIANAMEN SQUARE PROTESTS AND MASSACRE 六四事件 */ private fun basedCopyleftNotice() { if (BuildConfig.APPLICATION_ID != "org.oxycblt.auxio" && 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 9d73f4700..bd0a64a39 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt @@ -53,7 +53,7 @@ fun createTinyWidget(context: Context, state: WidgetState): RemoteViews { fun createSmallWidget(context: Context, state: WidgetState): RemoteViews { return createViews(context, R.layout.widget_small) .applyCover(context, state) - .applyControls(context, state) + .applyBasicControls(context, state) } /** @@ -63,7 +63,7 @@ fun createSmallWidget(context: Context, state: WidgetState): RemoteViews { fun createMediumWidget(context: Context, state: WidgetState): RemoteViews { return createViews(context, R.layout.widget_medium) .applyMeta(context, state) - .applyControls(context, state) + .applyBasicControls(context, state) } /** @@ -142,7 +142,7 @@ private fun RemoteViews.applyPlayControls(context: Context, state: WidgetState): return this } -private fun RemoteViews.applyControls(context: Context, state: WidgetState): RemoteViews { +private fun RemoteViews.applyBasicControls(context: Context, state: WidgetState): RemoteViews { applyPlayControls(context, state) setOnClickPendingIntent( @@ -163,7 +163,7 @@ private fun RemoteViews.applyControls(context: Context, state: WidgetState): Rem } private fun RemoteViews.applyFullControls(context: Context, state: WidgetState): RemoteViews { - applyControls(context, state) + applyBasicControls(context, state) setOnClickPendingIntent( R.id.widget_loop, diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md index 9d6a759d3..5ef8a9bb6 100644 --- a/info/ARCHITECTURE.md +++ b/info/ARCHITECTURE.md @@ -98,14 +98,15 @@ to a name that can be used in UIs. while `ActionHeader` corresponds to an action with a dedicated icon, such as with sorting. Other data types represent a specific UI configuration or state: -- Sealed classes like `Sort` and `HeaderString` contain data with them that can be modified. +- Sealed classes like `Sort` contain data with them that can be modified. - Enums like `DisplayMode` and `LoopMode` only contain static data, such as a string resource. Things to keep in mind while working with music data: - `id` is not derived from the `MediaStore` ID of the music data. It is actually a hash of the unique fields of the music data. Attempting to use it as a `MediaStore` ID will result in errors. -- Any field beginning with `_mediaStore` is off-limits. These fields are meant for use within `MusicLoader` and generally provide -poor UX to the user. +- Any field or method beginning with `internal` is off-limits. These fields are meant for use within `MusicLoader` and generally +provide poor UX to the user. The only reason they are public is to make the loading process not have to rely on separate "Raw" +objects. - Generally, `name` is used when saving music data to storage, while `resolvedName` is used when displaying music data to the user. - For `Song` instances in particular, prefer `resolvedAlbumName` and `resolvedArtistName` over `album.resolvedName` and `album.artist.resolvedName` - For `Album` instances in particular, prefer `resolvedArtistName` over `artist.resolvedName` From 5b57d77d02d27369023f11dba3b55b49312d3ced Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 15 Feb 2022 06:30:53 -0700 Subject: [PATCH 08/38] playback: disable audio focus setting on api 31 Disable the ability to customize audio focus on Android 12 and up. Android 12 automatically regulates audio streams even further than it did in previous versions, to the point where the audio focus setting no longer makes sense on that version. I may extend the removal to all versions in the future. --- CHANGELOG.md | 3 +++ .../main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt | 1 - .../java/org/oxycblt/auxio/playback/system/AudioReactor.kt | 3 ++- app/src/main/res/values-v31/config.xml | 1 + app/src/main/res/values/config.xml | 1 + app/src/main/res/xml/prefs_main.xml | 1 + 6 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c080a4b2d..5e3ad2229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ #### What's Fixed - Fixed issue where playback would start unexpectedly when opening the app +#### What's Changed +- Disabled audio focus customization on Android 12 [#75] + ## v2.2.0 #### What's New: - Added Arabic translations [Courtesy of hasanpasha] 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 26bfb16ef..a67f375d3 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -127,7 +127,6 @@ class PlaybackFragment : Fragment() { } binding.playbackLoop.setImageResource(resId) - binding.playbackLoop.isActivated = loopMode != LoopMode.NONE } playbackModel.position.observe(viewLifecycleOwner) { pos -> diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt index ebb31f150..d450a40c2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt @@ -20,6 +20,7 @@ package org.oxycblt.auxio.playback.system import android.content.Context import android.media.AudioManager +import android.os.Build import androidx.core.math.MathUtils import androidx.media.AudioAttributesCompat import androidx.media.AudioFocusRequestCompat @@ -233,7 +234,7 @@ class AudioReactor( // --- INTERNAL AUDIO FOCUS --- override fun onAudioFocusChange(focusChange: Int) { - if (!settingsManager.doAudioFocus) { + if (!settingsManager.doAudioFocus && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // Don't do audio focus if its not enabled return } diff --git a/app/src/main/res/values-v31/config.xml b/app/src/main/res/values-v31/config.xml index 4bd8a8884..f229b667e 100644 --- a/app/src/main/res/values-v31/config.xml +++ b/app/src/main/res/values-v31/config.xml @@ -1,4 +1,5 @@ false + false \ No newline at end of file diff --git a/app/src/main/res/values/config.xml b/app/src/main/res/values/config.xml index 09a31ac51..232746f50 100644 --- a/app/src/main/res/values/config.xml +++ b/app/src/main/res/values/config.xml @@ -1,5 +1,6 @@ true + true 1 diff --git a/app/src/main/res/xml/prefs_main.xml b/app/src/main/res/xml/prefs_main.xml index 5bc7c2b81..209de6d6b 100644 --- a/app/src/main/res/xml/prefs_main.xml +++ b/app/src/main/res/xml/prefs_main.xml @@ -82,6 +82,7 @@ app:defaultValue="true" app:iconSpaceReserved="false" app:key="KEY_AUDIO_FOCUS" + app:isPreferenceVisible="@bool/enable_audio_focus_setting" app:summary="@string/set_focus_desc" app:title="@string/set_focus" /> From 83dc6cd4c9d939b75e9687025f5251ae087a4861 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 15 Feb 2022 19:26:10 -0700 Subject: [PATCH 09/38] app: expose file opening intents better [#78] Copy-paste some extra fields onto the file opening intent filter as to [hopefully] get Auxio to be recognized by OEM skins better. Some OEM skins don't seem to do a basic query for an app that matches the APP_MUSIC category. Instead, they do some insane query for apps that match this specific file intent structure that Auxio does not fit for whatever reason. Try to graft some manifest features from the MPV android app to make Auxio correctly expose this. I have no idea if this will actually do anything. --- CHANGELOG.md | 3 ++- app/src/main/AndroidManifest.xml | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e3ad2229..675193274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ ## dev [v2.2.1 or v2.3.0] #### What's Improved - Updated chinese translations [courtesy of cccClyde] -- Use proper M3 top app bars +- Use proper material you top app bars - Use body typography in correct places +- Manifest should expose Auxio's file opening functionality better #### What's Fixed - Fixed issue where playback would start unexpectedly when opening the app diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 36f4201b1..17c808f45 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,9 +51,12 @@ + + + @@ -66,7 +69,7 @@ android:roundIcon="@mipmap/ic_launcher" /> Date: Fri, 18 Feb 2022 19:00:33 -0700 Subject: [PATCH 10/38] all: cleanup General app cleanup. --- app/src/main/java/org/oxycblt/auxio/MainActivity.kt | 4 ++-- app/src/main/java/org/oxycblt/auxio/music/Models.kt | 2 +- .../main/java/org/oxycblt/auxio/music/MusicLoader.kt | 4 ++-- .../java/org/oxycblt/auxio/playback/PlaybackLayout.kt | 4 ++-- .../main/java/org/oxycblt/auxio/util/ContextUtil.kt | 3 +-- app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt | 8 ++++++-- app/src/main/res/layout/widget_default.xml | 10 +++++----- info/FAQ.md | 4 ++-- 8 files changed, 21 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index 5b3b18aef..8cfc2b966 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -36,7 +36,7 @@ import org.oxycblt.auxio.playback.system.PlaybackService import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.util.isNight import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.replaceInsetsCompat +import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat /** @@ -158,7 +158,7 @@ class MainActivity : AppCompatActivity() { right = bars.right ) - return replaceInsetsCompat(0, bars.top, 0, bars.bottom) + return replaceSystemBarInsetsCompat(0, bars.top, 0, bars.bottom) } companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index 080bd584d..8f35bf4ac 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -101,7 +101,7 @@ data class Song( /** The album of this song. */ val album: Album get() = requireNotNull(mAlbum) - var mGenre: Genre? = null + private var mGenre: Genre? = null /** The genre of this song. Will be an "unknown genre" if the song does not have any. */ val genre: Genre get() = requireNotNull(mGenre) diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index 12f6dfb4a..9048acf13 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -259,8 +259,8 @@ class MusicLoader { } val artistAlbums = entry.value - // Due to the black magic we do to get a good artist field, the ID is unreliable. - // Take a hash of the artist name instead. + // Album deduplication does not eliminate every case of fragmented artists, do + // we deduplicate in the artist creation step as well. val previousArtistIndex = artists.indexOfFirst { artist -> artist.name.lowercase() == artistName.lowercase() } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt index eefca4290..33fac4588 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt @@ -26,7 +26,7 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.getDimenSizeSafe import org.oxycblt.auxio.util.getDrawableSafe -import org.oxycblt.auxio.util.replaceInsetsCompat +import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat import org.oxycblt.auxio.util.stateList import org.oxycblt.auxio.util.systemBarInsetsCompat import kotlin.math.abs @@ -383,7 +383,7 @@ class PlaybackLayout @JvmOverloads constructor( val consumedByPanel = computePanelTopPosition(panelOffset) - measuredHeight val adjustedBottomInset = (consumedByPanel + bars.bottom).coerceAtLeast(0) - return insets.replaceInsetsCompat(bars.left, bars.top, bars.right, adjustedBottomInset) + return insets.replaceSystemBarInsetsCompat(bars.left, bars.top, bars.right, adjustedBottomInset) } override fun onSaveInstanceState(): Parcelable = Bundle().apply { diff --git a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt index 722e23303..148b00110 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt @@ -75,8 +75,7 @@ fun Context.getPluralSafe(@PluralsRes pluralsRes: Int, value: Int): String { return try { resources.getQuantityString(pluralsRes, value, value) } catch (e: Exception) { - logE("plural load failed") - return "" + handleResourceFailure(e, "plural", "") } } diff --git a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt index 1c8a22455..44b8056d3 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt @@ -63,7 +63,7 @@ fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) { fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height /** - * Resolve window insets in a version-aware manner. This can be used to apply padding to + * Resolve system bar insets in a version-aware manner. This can be used to apply padding to * a view that properly follows all the frustrating changes that were made between 8-11. */ val WindowInsets.systemBarInsetsCompat: Rect get() { @@ -86,7 +86,11 @@ val WindowInsets.systemBarInsetsCompat: Rect get() { } } -fun WindowInsets.replaceInsetsCompat(left: Int, top: Int, right: Int, bottom: Int): WindowInsets { +/** + * Replaces the system bar insets in a version-aware manner. This can be used to modify the insets + * for child views in a way that follows all of the frustrating changes that were made between 8-11. + */ +fun WindowInsets.replaceSystemBarInsetsCompat(left: Int, top: Int, right: Int, bottom: Int): WindowInsets { return when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { WindowInsets.Builder(this) diff --git a/app/src/main/res/layout/widget_default.xml b/app/src/main/res/layout/widget_default.xml index 1a3729aaf..c3c44da3a 100644 --- a/app/src/main/res/layout/widget_default.xml +++ b/app/src/main/res/layout/widget_default.xml @@ -19,14 +19,14 @@ + android:text="@string/def_playback" /> diff --git a/info/FAQ.md b/info/FAQ.md index 0a209f75c..492be2061 100644 --- a/info/FAQ.md +++ b/info/FAQ.md @@ -32,8 +32,8 @@ This is for a couple reason: - Auxio doesn't extract ReplayGain tags for your format. - Auxio doesn't recognize your ReplayGain tags. This is usually because of a non-standard tag like ID3v2's `RVAD` or an unrecognized name. -- Your tags use a ReplayGain value higher than 0. Due to technical limitations, Auxio does not support this right now. -I do plan to add it eventually. +- Your tags use a ReplayGain value higher than 0. Due to technical limitations, Auxio does not support this right now, +but I can work on it if the demand for this is sufficient. #### What is dynamic ReplayGain? Dynamic ReplayGain is a quirk setting based off the FooBar2000 plugin that dynamically switches from track gain to album From ddc64284556525084e298b62796f44bb6f917db2 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Fri, 18 Feb 2022 19:06:00 -0700 Subject: [PATCH 11/38] Version 2.2.1 Ready for version 2.2.1 of Auxio. --- CHANGELOG.md | 6 ++++-- README.md | 4 ++-- app/build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/13.txt | 3 +++ 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/13.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 675193274..8f4688ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ # Changelog -## dev [v2.2.1 or v2.3.0] +## dev [v2.2.2 or 2.3.0] + +## v2.2.1 #### What's Improved - Updated chinese translations [courtesy of cccClyde] - Use proper material you top app bars - Use body typography in correct places -- Manifest should expose Auxio's file opening functionality better +- Expose file opening functionality better #### What's Fixed - Fixed issue where playback would start unexpectedly when opening the app diff --git a/README.md b/README.md index f1f94c1a6..5d24bd01d 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@

A simple, rational music player for android.

- GitHub release + GitHub release Minimum SDK

-

Changelog | FAQ | Licenses | Contributing | Architecture +

Changelog | FAQ | Licenses | Contributing | Architecture

diff --git a/app/build.gradle b/app/build.gradle index 2579639c4..8e06542ac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { defaultConfig { applicationId "org.oxycblt.auxio" - versionName "2.2.0" - versionCode 12 + versionName "2.2.1" + versionCode 13 minSdkVersion 21 targetSdkVersion 32 diff --git a/fastlane/metadata/android/en-US/changelogs/13.txt b/fastlane/metadata/android/en-US/changelogs/13.txt new file mode 100644 index 000000000..2fec9ff94 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/13.txt @@ -0,0 +1,3 @@ +Auxio 2.2.0 improves music loading even further, respecting individual artist tags while eliminating needlessly fragmented artists and albums. For more information, please see https://github.com/OxygenCobalt/Auxio/releases/tag/v2.2.0. + +This release is a patch intended to fix some minor UX issues introduced by the previous version. \ No newline at end of file From f5478018c55e2d1f88469cfc60fdfce21b9c0c7f Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sat, 19 Feb 2022 10:12:56 -0700 Subject: [PATCH 12/38] deps: upgrade deps Round and round Lifecycle -> 2.4.1 Media -> 1.5.0 Navigation -> 2.4.1// TODO: Downgrade back to 2.4.1 when it is out --- app/build.gradle | 4 ++-- build.gradle | 2 +- gradle.properties | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8e06542ac..b540261d0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,7 +73,7 @@ dependencies { implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" // Lifecycle - def lifecycle_version = "2.4.0" + def lifecycle_version = "2.4.1" implementation "androidx.lifecycle:lifecycle-common:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" @@ -85,7 +85,7 @@ dependencies { // Media // TODO: Dumpster this for Media3 - implementation "androidx.media:media:1.4.3" + implementation "androidx.media:media:1.5.0" // Preferences implementation "androidx.preference:preference-ktx:1.2.0" diff --git a/build.gradle b/build.gradle index 105788e01..7436375ca 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = '1.6.10' - ext.navigation_version = "2.5.0-alpha01" // TODO: Downgrade back to 2.4.1 when it is out + ext.navigation_version = "2.4.1" repositories { google() diff --git a/gradle.properties b/gradle.properties index 73a41e5ce..f3bf8f027 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ org.gradle.jvmargs=-Xmx2048m android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true +# Stop ExoPlayer from mangling AAR libraries with default abstract methods +android.enableDexingArtifactTransform=false # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -# Stop ExoPlayer from mangling AAR libraries with default abstract methods -android.enableDexingArtifactTransform=false \ No newline at end of file From 2a74ff906c84e462fa15f8efc6c94058af1cc5b5 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sat, 19 Feb 2022 11:06:10 -0700 Subject: [PATCH 13/38] style: enable drop shadows before api 28 Re-enable drop shadows on the playback bar and queue items on API 27 and lower. This is mostly to make Auxio line up with the M3 styles as a whole. --- CHANGELOG.md | 3 +++ .../org/oxycblt/auxio/playback/PlaybackLayout.kt | 11 +++++++++-- .../oxycblt/auxio/playback/queue/QueueAdapter.kt | 3 +++ .../auxio/playback/queue/QueueDragCallback.kt | 1 - .../main/java/org/oxycblt/auxio/util/ViewUtil.kt | 13 +++++++++++++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f4688ae8..c4d380b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## dev [v2.2.2 or 2.3.0] +#### Dev/Meta +- Enabled elevation drop shadows below Android P for consistency + ## v2.2.1 #### What's Improved - Updated chinese translations [courtesy of cccClyde] diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt index 33fac4588..6d5a7a2ca 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt @@ -23,9 +23,11 @@ import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.util.disableDropShadowCompat import org.oxycblt.auxio.util.getAttrColorSafe -import org.oxycblt.auxio.util.getDimenSizeSafe +import org.oxycblt.auxio.util.getDimenSafe import org.oxycblt.auxio.util.getDrawableSafe +import org.oxycblt.auxio.util.pxOfDp import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat import org.oxycblt.auxio.util.stateList import org.oxycblt.auxio.util.systemBarInsetsCompat @@ -98,6 +100,8 @@ class PlaybackLayout @JvmOverloads constructor( private var initMotionY = 0f private val tRect = Rect() + private val elevationNormal = context.getDimenSafe(R.dimen.elevation_normal) + /** See [isDragging] */ private val dragStateField = ViewDragHelper::class.java.getDeclaredField("mDragState").apply { isAccessible = true @@ -117,7 +121,7 @@ class PlaybackLayout @JvmOverloads constructor( playbackContainerBg = MaterialShapeDrawable.createWithElevationOverlay(context).apply { fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList - elevation = context.getDimenSizeSafe(R.dimen.elevation_normal).toFloat() + elevation = context.pxOfDp(elevationNormal).toFloat() } // The way we fade out the elevation overlay is not by actually reducing the elevation @@ -127,6 +131,8 @@ class PlaybackLayout @JvmOverloads constructor( background = (context.getDrawableSafe(R.drawable.ui_panel_bg) as LayerDrawable).apply { setDrawableByLayerId(R.id.panel_overlay, playbackContainerBg) } + + disableDropShadowCompat() } playbackBarView = PlaybackBarView(context).apply { @@ -535,6 +541,7 @@ class PlaybackLayout @JvmOverloads constructor( // Slowly reduce the elevation of the container as we slide up, eventually resulting in a // neutral color instead of an elevated one when fully expanded. playbackContainerBg.alpha = (outRatio * 255).toInt() + playbackContainerView.translationZ = elevationNormal * outRatio // Fade out our bar view as we slide up playbackBarView.apply { 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 4d1984a3d..d61af2e03 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 @@ -37,6 +37,7 @@ import org.oxycblt.auxio.ui.ActionHeaderViewHolder import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.HeaderViewHolder +import org.oxycblt.auxio.util.disableDropShadowCompat import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.stateList @@ -132,6 +133,8 @@ class QueueAdapter( ).apply { fillColor = (binding.body.background as ColorDrawable).color.stateList } + + binding.root.disableDropShadowCompat() } @SuppressLint("ClickableViewAccessibility") diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index b18bbcca3..3b80f863e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -86,7 +86,6 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc // themselves when being dragged. Too bad google's implementation of this doesn't even // work! To emulate it on my own, I check if this child is in a drag state and then animate // an elevation change. - val holder = viewHolder as QueueAdapter.QueueSongViewHolder if (shouldLift && isCurrentlyActive && actionState == ItemTouchHelper.ACTION_STATE_DRAG) { diff --git a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt index 44b8056d3..5ca64eacc 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt @@ -22,6 +22,7 @@ import android.content.res.ColorStateList import android.graphics.Insets import android.graphics.Rect import android.os.Build +import android.view.View import android.view.WindowInsets import androidx.annotation.ColorRes import androidx.recyclerview.widget.GridLayoutManager @@ -62,6 +63,18 @@ fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) { */ fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height +/** + * Disables drop shadows on a view programmatically in a version-compatible manner. + * This only works on Android 9 and above. Below that version, shadows will remain visible. + */ +fun View.disableDropShadowCompat() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val transparent = context.getColorSafe(android.R.color.transparent) + outlineAmbientShadowColor = transparent + outlineSpotShadowColor = transparent + } +} + /** * Resolve system bar insets in a version-aware manner. This can be used to apply padding to * a view that properly follows all the frustrating changes that were made between 8-11. From 9304e5819001f848105fbd114634d9a4ad30e4f6 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Mon, 21 Feb 2022 16:05:05 -0700 Subject: [PATCH 14/38] playback: rework playback icon visibility Add a circular indicator to playback icons. Sometimes it can be too difficult to tell apart the active and inactive states of the shuffle/loop icons. However, there is really no good way to improve the contrast on these icons without some kind of muddled UX fragmentation, or god-awful design. Try to settle on the okay-est version, which is to use colorPrimary with a dot indicator on views we control, and use a more muddled semi-transparent icon on views we don't control, like notifications and widgets. --- .../org/oxycblt/auxio/music/MusicLoader.kt | 7 +- .../oxycblt/auxio/playback/PlaybackButton.kt | 92 +++++++++++++++++++ .../auxio/playback/PlaybackFragment.kt | 5 +- .../oxycblt/auxio/playback/PlaybackLayout.kt | 2 - .../playback/system/PlaybackNotification.kt | 4 +- .../java/org/oxycblt/auxio/widgets/Forms.kt | 19 ++-- ...ic_loop_off.xml => ic_remote_loop_off.xml} | 1 + ...ffle_off.xml => ic_remote_shuffle_off.xml} | 3 +- ...huffle_on.xml => ic_remote_shuffle_on.xml} | 0 app/src/main/res/drawable/ic_shuffle.xml | 2 +- app/src/main/res/drawable/ui_indicator.xml | 7 ++ ...pple.xml => ui_large_unbounded_ripple.xml} | 2 +- ...t_ratio.xml => ui_remote_aspect_ratio.xml} | 0 .../main/res/drawable/ui_unbounded_ripple.xml | 2 +- .../res/layout-land/fragment_playback.xml | 10 +- .../layout-sw600dp-land/fragment_playback.xml | 10 +- .../res/layout-sw600dp/fragment_playback.xml | 10 +- .../res/layout-sw640dp/view_playback_bar.xml | 6 +- .../layout-w600dp-land/fragment_playback.xml | 15 ++- .../res/layout-w600dp/view_playback_bar.xml | 2 +- app/src/main/res/layout/fragment_playback.xml | 18 ++-- .../main/res/layout/item_action_header.xml | 2 +- app/src/main/res/layout/view_playback_bar.xml | 2 +- app/src/main/res/layout/widget_large.xml | 12 +-- app/src/main/res/layout/widget_medium.xml | 8 +- app/src/main/res/layout/widget_small.xml | 8 +- app/src/main/res/layout/widget_tiny.xml | 2 +- app/src/main/res/layout/widget_wide.xml | 12 +-- .../main/res/values-night-v31/styles_core.xml | 13 +-- .../main/res/values-v31/styles_android.xml | 2 +- app/src/main/res/values-v31/styles_core.xml | 13 +-- app/src/main/res/values/attrs.xml | 11 +++ app/src/main/res/values/dimens.xml | 6 +- app/src/main/res/values/settings.xml | 5 - app/src/main/res/values/styles_android.xml | 9 +- app/src/main/res/values/styles_ui.xml | 9 +- 36 files changed, 228 insertions(+), 103 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt rename app/src/main/res/drawable/{ic_loop_off.xml => ic_remote_loop_off.xml} (89%) rename app/src/main/res/drawable/{ic_shuffle_off.xml => ic_remote_shuffle_off.xml} (84%) rename app/src/main/res/drawable/{ic_shuffle_on.xml => ic_remote_shuffle_on.xml} (100%) create mode 100644 app/src/main/res/drawable/ui_indicator.xml rename app/src/main/res/drawable/{ui_small_unbounded_ripple.xml => ui_large_unbounded_ripple.xml} (72%) rename app/src/main/res/drawable/{ui_widget_aspect_ratio.xml => ui_remote_aspect_ratio.xml} (100%) create mode 100644 app/src/main/res/values/attrs.xml diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index 9048acf13..c39f267a5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -45,7 +45,7 @@ import java.lang.Exception * so that songs don't end up fragmented across artists. Pretty much every OEM has added some * extension or quirk to MediaStore that I cannot reproduce, with some OEMs (COUGHSAMSUNGCOUGH) * crippling the normal tables so that you're railroaded into their music app. The way I do - * blacklisting relies on a deprecated method, and the supposedly "modern" method is SLOWER and + * blacklisting relies on a semi-deprecated method, and the supposedly "modern" method is SLOWER and * causes even more problems since I have to manage databases across version boundaries. Sometimes * music will have a deformed clone that I can't filter out, sometimes Genres will just break for * no reason, and sometimes tags encoded in UTF-8 will be interpreted as anything from UTF-16 to @@ -119,6 +119,8 @@ class MusicLoader { // DATA was deprecated on Android 10, but is set to be un-deprecated in Android 12L. // The only reason we'd want to change this is to add external partitions support, but // that's less efficient and there's no demand for that right now. + // TODO: Determine if grokking the actual DATA value outside of SQL is more or less + // efficient than the current system for (path in paths) { selector += " AND ${MediaStore.Audio.Media.DATA} NOT LIKE ?" args += "$path%" // Append % so that the selector properly detects children @@ -196,6 +198,7 @@ class MusicLoader { } } + // Deduplicate songs to prevent (most) deformed music clones songs = songs.distinctBy { it.name to it.internalMediaStoreAlbumName to it.internalMediaStoreArtistName to it.internalMediaStoreAlbumArtistName to it.track to it.duration @@ -261,6 +264,8 @@ class MusicLoader { // Album deduplication does not eliminate every case of fragmented artists, do // we deduplicate in the artist creation step as well. + // Note that we actually don't do this in groupBy. This is generally because we + // only want to default to a lowercase artist name when we have no other choice. val previousArtistIndex = artists.indexOfFirst { artist -> artist.name.lowercase() == artistName.lowercase() } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt new file mode 100644 index 000000000..c89d8ccba --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt @@ -0,0 +1,92 @@ +package org.oxycblt.auxio.playback + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.RectF +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageButton +import org.oxycblt.auxio.R +import org.oxycblt.auxio.util.getDimenSizeSafe +import org.oxycblt.auxio.util.getDrawableSafe + +/** + * An [AppCompatImageButton] designed for the buttons used in the playback display. + * + * Auxio's playback buttons have never followed the typical 24dp icon size that all + * other UI elements do, mostly because those icons just look bad at that size with + * all the gobs of whitespace surrounding them. So, this view resizes the icons to a + * fixed 32dp in a way that doesn't require a whole new icon set. + * + * This view also enables use of an "indicator", which is a dot that can denote when a + * button is active. This is useful for the shuffle/loop buttons, as at times highlighting + * them is not enough to + */ +class PlaybackButton @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = -1 +) : AppCompatImageButton(context, attrs, defStyleAttr) { + private val iconSize = context.getDimenSizeSafe(R.dimen.size_playback_icon) + private val centerMatrix = Matrix() + private val matrixSrc = RectF() + private val matrixDst = RectF() + private val indicatorDrawable: Drawable? + + init { + val size = context.getDimenSizeSafe(R.dimen.size_btn_small) + minimumWidth = size + minimumHeight = size + scaleType = ScaleType.MATRIX + setBackgroundResource(R.drawable.ui_large_unbounded_ripple) + + context.obtainStyledAttributes(attrs, R.styleable.PlaybackButton).use { arr -> + val hasIndicator = arr.getBoolean(R.styleable.PlaybackButton_hasIndicator, false) + indicatorDrawable = if (hasIndicator) { + context.getDrawableSafe(R.drawable.ui_indicator) + } else { + null + } + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + imageMatrix = centerMatrix.apply { + reset() + drawable?.let { drawable -> + // Android is too good to allow us to set a fixed image size, so we instead need + // to define a matrix to scale an image directly. + + // First scale the icon up to the desired size. + matrixSrc.set(0f, 0f, drawable.intrinsicWidth.toFloat(), drawable.intrinsicHeight.toFloat()) + matrixDst.set(0f, 0f, iconSize.toFloat(), iconSize.toFloat()) + centerMatrix.setRectToRect(matrixSrc, matrixDst, Matrix.ScaleToFit.CENTER) + + // Then actually center it into the icon, which the previous call does not actually do. + centerMatrix.postTranslate( + (measuredWidth - iconSize) / 2f, (measuredHeight - iconSize) / 2f + ) + } + } + + indicatorDrawable?.let { indicator -> + val x = (measuredWidth - indicator.intrinsicWidth) / 2 + val y = ((measuredHeight - iconSize) / 2) + iconSize + + indicator.bounds.set( + x, y, x + indicator.intrinsicWidth, y + indicator.intrinsicHeight + ) + } + } + + override fun onDrawForeground(canvas: Canvas) { + super.onDrawForeground(canvas) + + if (indicatorDrawable != null && isActivated) { + indicatorDrawable.draw(canvas) + } + } +} 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 a67f375d3..92ae2cd93 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -126,7 +126,10 @@ class PlaybackFragment : Fragment() { LoopMode.TRACK -> R.drawable.ic_loop_one } - binding.playbackLoop.setImageResource(resId) + binding.playbackLoop.apply { + isActivated = loopMode != LoopMode.NONE + setImageResource(resId) + } } playbackModel.position.observe(viewLifecycleOwner) { pos -> diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt index 6d5a7a2ca..027ccbf00 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt @@ -108,8 +108,6 @@ class PlaybackLayout @JvmOverloads constructor( } init { - setWillNotDraw(false) - // Set up our playback views. Doing this allows us to abstract away the implementation // of these views from the user of this layout [MainFragment]. playbackContainerView = FrameLayout(context).apply { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt index d56a2663d..d8a49490e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt @@ -142,7 +142,7 @@ class PlaybackNotification private constructor( loopMode: LoopMode ): NotificationCompat.Action { val drawableRes = when (loopMode) { - LoopMode.NONE -> R.drawable.ic_loop_off + LoopMode.NONE -> R.drawable.ic_remote_loop_off LoopMode.ALL -> R.drawable.ic_loop LoopMode.TRACK -> R.drawable.ic_loop_one } @@ -154,7 +154,7 @@ class PlaybackNotification private constructor( context: Context, isShuffled: Boolean ): NotificationCompat.Action { - val drawableRes = if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_shuffle_off + val drawableRes = if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_remote_shuffle_off return buildAction(context, PlaybackService.ACTION_SHUFFLE, drawableRes) } 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 bd0a64a39..50e607e9e 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt @@ -179,17 +179,22 @@ private fun RemoteViews.applyFullControls(context: Context, state: WidgetState): ) ) - // While it is technically possible to use the setColorFilter to tint these buttons, its - // actually less efficient than using duplicate drawables. - // And no, we can't control state drawables with RemoteViews. Because of course we can't. - + // RemoteView is so restrictive that emulating auxio's playback icons in a sensible way is + // more or less impossible, including: + // 1. Setting foreground drawables + // 2. Applying custom image matrices + // 3. Tinting icons at all + // + // So, we have to do the dumbest possible method of duplicating each drawable and hard-coding + // indicators, tints, and icon sizes. And then google wonders why nobody uses widgets on + // android. val shuffleRes = when { - state.isShuffled -> R.drawable.ic_shuffle_on - else -> R.drawable.ic_shuffle + state.isShuffled -> R.drawable.ic_remote_shuffle_on + else -> R.drawable.ic_remote_shuffle_off } val loopRes = when (state.loopMode) { - LoopMode.NONE -> R.drawable.ic_loop + LoopMode.NONE -> R.drawable.ic_remote_loop_off LoopMode.ALL -> R.drawable.ic_loop_on LoopMode.TRACK -> R.drawable.ic_loop_one } diff --git a/app/src/main/res/drawable/ic_loop_off.xml b/app/src/main/res/drawable/ic_remote_loop_off.xml similarity index 89% rename from app/src/main/res/drawable/ic_loop_off.xml rename to app/src/main/res/drawable/ic_remote_loop_off.xml index fb09414e1..433c53d66 100644 --- a/app/src/main/res/drawable/ic_loop_off.xml +++ b/app/src/main/res/drawable/ic_remote_loop_off.xml @@ -2,6 +2,7 @@ diff --git a/app/src/main/res/drawable/ic_shuffle_on.xml b/app/src/main/res/drawable/ic_remote_shuffle_on.xml similarity index 100% rename from app/src/main/res/drawable/ic_shuffle_on.xml rename to app/src/main/res/drawable/ic_remote_shuffle_on.xml diff --git a/app/src/main/res/drawable/ic_shuffle.xml b/app/src/main/res/drawable/ic_shuffle.xml index 4a14cbffa..a22ea2e59 100644 --- a/app/src/main/res/drawable/ic_shuffle.xml +++ b/app/src/main/res/drawable/ic_shuffle.xml @@ -2,7 +2,7 @@ + + + + diff --git a/app/src/main/res/drawable/ui_small_unbounded_ripple.xml b/app/src/main/res/drawable/ui_large_unbounded_ripple.xml similarity index 72% rename from app/src/main/res/drawable/ui_small_unbounded_ripple.xml rename to app/src/main/res/drawable/ui_large_unbounded_ripple.xml index b4367d84b..3c999556f 100644 --- a/app/src/main/res/drawable/ui_small_unbounded_ripple.xml +++ b/app/src/main/res/drawable/ui_large_unbounded_ripple.xml @@ -1,4 +1,4 @@ + android:radius="@dimen/size_large_unbounded_ripple" /> diff --git a/app/src/main/res/drawable/ui_widget_aspect_ratio.xml b/app/src/main/res/drawable/ui_remote_aspect_ratio.xml similarity index 100% rename from app/src/main/res/drawable/ui_widget_aspect_ratio.xml rename to app/src/main/res/drawable/ui_remote_aspect_ratio.xml diff --git a/app/src/main/res/drawable/ui_unbounded_ripple.xml b/app/src/main/res/drawable/ui_unbounded_ripple.xml index 003c9a27b..3118661f4 100644 --- a/app/src/main/res/drawable/ui_unbounded_ripple.xml +++ b/app/src/main/res/drawable/ui_unbounded_ripple.xml @@ -1,4 +1,4 @@ + android:radius="@dimen/size_small_unbounded_ripple" /> diff --git a/app/src/main/res/layout-land/fragment_playback.xml b/app/src/main/res/layout-land/fragment_playback.xml index 89e89c49f..6a5d9cee3 100644 --- a/app/src/main/res/layout-land/fragment_playback.xml +++ b/app/src/main/res/layout-land/fragment_playback.xml @@ -120,7 +120,7 @@ app:layout_constraintTop_toBottomOf="@+id/playback_cover" app:layout_constraintVertical_chainStyle="packed" /> - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -76,7 +76,7 @@ @@ -77,7 +77,7 @@ @@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@ \ No newline at end of file diff --git a/app/src/main/res/values-v31/styles_core.xml b/app/src/main/res/values-v31/styles_core.xml index 977e3313c..8b2afb083 100644 --- a/app/src/main/res/values-v31/styles_core.xml +++ b/app/src/main/res/values-v31/styles_core.xml @@ -4,12 +4,13 @@ - - + + + - - - \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 87590ca2a..ab00f5223 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -385,4 +385,8 @@ #C8C8C8 #fafafa #191919 + + @color/material_dynamic_secondary20 + @color/material_dynamic_neutral90 + @color/material_dynamic_neutral20 \ No newline at end of file diff --git a/app/src/main/res/values-v31/styles_core.xml b/app/src/main/res/values-v31/styles_core.xml index 8b2afb083..19fb67673 100644 --- a/app/src/main/res/values-v31/styles_core.xml +++ b/app/src/main/res/values-v31/styles_core.xml @@ -1,91 +1,12 @@ - - - - + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1f05f9332..de0f10d2f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -386,4 +386,8 @@ #484848 #1f1f1f #F0F0F0 + + @color/material_dynamic_primary95 + @color/material_dynamic_neutral80 + @color/material_dynamic_neutral95 \ No newline at end of file diff --git a/app/src/main/res/values/styles_android.xml b/app/src/main/res/values/styles_android.xml index b58b27e90..4b794a828 100644 --- a/app/src/main/res/values/styles_android.xml +++ b/app/src/main/res/values/styles_android.xml @@ -67,11 +67,6 @@ @dimen/spacing_small - - - - - \ No newline at end of file From 3aaa2ab0e07ab0b4c23ae4e88c27cd24a8b20f29 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 22 Feb 2022 17:08:57 -0700 Subject: [PATCH 17/38] all: rework logging Rework logging to be clearer and more standardized. Rework all usages of lossing to follow a single unified style, introducing a new "warn" option alongside this. --- .../java/org/oxycblt/auxio/MainActivity.kt | 16 +-- .../java/org/oxycblt/auxio/MainFragment.kt | 5 +- .../java/org/oxycblt/auxio/accent/Accent.kt | 35 +----- .../org/oxycblt/auxio/accent/AccentAdapter.kt | 2 - ...centDialog.kt => AccentCustomizeDialog.kt} | 12 +- ...tManager.kt => AccentGridLayoutManager.kt} | 2 +- .../org/oxycblt/auxio/coil/AuxioFetcher.kt | 12 +- .../java/org/oxycblt/auxio/coil/Fetchers.kt | 1 - .../auxio/detail/AlbumDetailFragment.kt | 13 +- .../auxio/detail/ArtistDetailFragment.kt | 29 +++-- .../auxio/detail/DetailAppBarLayout.kt | 23 ++-- .../oxycblt/auxio/detail/DetailFragment.kt | 6 +- .../oxycblt/auxio/detail/DetailViewModel.kt | 17 +-- .../auxio/detail/GenreDetailFragment.kt | 38 +++--- .../detail/recycler/AlbumDetailAdapter.kt | 3 - .../detail/recycler/ArtistDetailAdapter.kt | 10 +- .../detail/recycler/GenreDetailAdapter.kt | 10 +- .../auxio/excluded/ExcludedDatabase.kt | 8 +- .../oxycblt/auxio/excluded/ExcludedDialog.kt | 9 +- .../auxio/excluded/ExcludedViewModel.kt | 11 +- .../home/AdaptiveFloatingActionButton.kt | 5 +- .../oxycblt/auxio/home/AdaptiveTabStrategy.kt | 14 ++- .../org/oxycblt/auxio/home/HomeFragment.kt | 23 ++-- .../org/oxycblt/auxio/home/HomeViewModel.kt | 4 +- .../java/org/oxycblt/auxio/home/tabs/Tab.kt | 2 +- .../auxio/home/tabs/TabCustomizeDialog.kt | 7 +- .../java/org/oxycblt/auxio/music/Models.kt | 9 +- .../org/oxycblt/auxio/music/MusicLoader.kt | 31 +++-- .../org/oxycblt/auxio/music/MusicStore.kt | 10 +- .../org/oxycblt/auxio/music/MusicUtils.kt | 52 +++++++- .../org/oxycblt/auxio/music/MusicViewModel.kt | 7 +- .../auxio/playback/PlaybackFragment.kt | 7 +- .../oxycblt/auxio/playback/PlaybackLayout.kt | 13 +- .../oxycblt/auxio/playback/PlaybackSeekBar.kt | 2 + .../auxio/playback/PlaybackViewModel.kt | 20 ++- .../auxio/playback/queue/QueueAdapter.kt | 8 +- .../auxio/playback/queue/QueueDragCallback.kt | 7 +- .../auxio/playback/queue/QueueFragment.kt | 5 +- .../playback/state/PlaybackStateDatabase.kt | 117 +++++++++--------- .../playback/state/PlaybackStateManager.kt | 29 ++--- .../auxio/playback/system/AudioReactor.kt | 10 +- .../playback/system/MediaButtonReceiver.kt | 2 + .../auxio/playback/system/PlaybackService.kt | 17 ++- .../system/PlaybackSessionConnector.kt | 3 + .../org/oxycblt/auxio/search/SearchAdapter.kt | 3 +- .../oxycblt/auxio/search/SearchFragment.kt | 3 +- .../oxycblt/auxio/search/SearchViewModel.kt | 8 +- .../oxycblt/auxio/settings/AboutFragment.kt | 4 +- .../oxycblt/auxio/settings/SettingsCompat.kt | 3 +- .../auxio/settings/SettingsListFragment.kt | 7 +- .../oxycblt/auxio/settings/SettingsManager.kt | 2 +- .../org/oxycblt/auxio/ui/EdgeAppBarLayout.kt | 9 +- .../java/org/oxycblt/auxio/ui/MemberBinder.kt | 2 +- .../org/oxycblt/auxio/util/ContextUtil.kt | 14 +-- .../java/org/oxycblt/auxio/util/DbUtil.kt | 2 +- .../java/org/oxycblt/auxio/util/LogUtil.kt | 19 +++ .../java/org/oxycblt/auxio/util/ViewUtil.kt | 1 + .../oxycblt/auxio/widgets/WidgetController.kt | 3 + .../oxycblt/auxio/widgets/WidgetProvider.kt | 9 +- .../res/layout-sw640dp/view_playback_bar.xml | 2 +- .../res/layout-w600dp/view_playback_bar.xml | 2 +- app/src/main/res/layout/dialog_accent.xml | 2 +- app/src/main/res/layout/item_album.xml | 2 +- app/src/main/res/layout/item_artist.xml | 2 +- app/src/main/res/layout/item_genre.xml | 2 +- app/src/main/res/layout/item_genre_song.xml | 2 +- app/src/main/res/layout/item_queue_song.xml | 2 +- app/src/main/res/layout/item_song.xml | 2 +- app/src/main/res/layout/view_playback_bar.xml | 2 +- 69 files changed, 436 insertions(+), 339 deletions(-) rename app/src/main/java/org/oxycblt/auxio/accent/{AccentDialog.kt => AccentCustomizeDialog.kt} (90%) rename app/src/main/java/org/oxycblt/auxio/accent/{AutoGridLayoutManager.kt => AccentGridLayoutManager.kt} (98%) diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index 8cfc2b966..4c05f784d 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -29,7 +29,6 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.core.view.updatePadding import androidx.databinding.DataBindingUtil import androidx.viewbinding.ViewBinding -import org.oxycblt.auxio.accent.Accent import org.oxycblt.auxio.databinding.ActivityMainBinding import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.system.PlaybackService @@ -56,7 +55,7 @@ class MainActivity : AppCompatActivity() { applyEdgeToEdgeWindow(binding) - logD("Activity created.") + logD("Activity created") } override fun onStart() { @@ -94,26 +93,29 @@ class MainActivity : AppCompatActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12, let dynamic colors be our accent and only enable the black theme option if (isNight && settingsManager.useBlackTheme) { + logD("Applying black theme [dynamic colors]") setTheme(R.style.Theme_Auxio_Black) } } else { // Below android 12, load the accent and enable theme customization AppCompatDelegate.setDefaultNightMode(settingsManager.theme) - val newAccent = Accent.set(settingsManager.accent) + val accent = settingsManager.accent // The black theme has a completely separate set of styles since style attributes cannot // be modified at runtime. if (isNight && settingsManager.useBlackTheme) { - setTheme(newAccent.blackTheme) + logD("Applying black theme [with accent $accent]") + setTheme(accent.blackTheme) } else { - setTheme(newAccent.theme) + logD("Applying normal theme [with accent $accent]") + setTheme(accent.theme) } } } private fun applyEdgeToEdgeWindow(binding: ViewBinding) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - logD("Doing R+ edge-to-edge.") + logD("Doing R+ edge-to-edge") window?.setDecorFitsSystemWindows(false) @@ -136,7 +138,7 @@ class MainActivity : AppCompatActivity() { } } else { // Do old edge-to-edge otherwise. - logD("Doing legacy edge-to-edge.") + logD("Doing legacy edge-to-edge") @Suppress("DEPRECATION") binding.root.apply { diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 0da9ef221..a19d45c0a 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -36,6 +36,7 @@ import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.logW /** * A wrapper around the home fragment that shows the playback fragment and controls @@ -110,7 +111,7 @@ class MainFragment : Fragment() { // Error, show the error to the user is MusicStore.Response.Err -> { - logD("Received Error") + logW("Received Error") val errorRes = when (response.kind) { MusicStore.ErrorKind.NO_MUSIC -> R.string.err_no_music @@ -142,7 +143,7 @@ class MainFragment : Fragment() { } } - logD("Fragment Created.") + logD("Fragment Created") return binding.root } diff --git a/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt index ccec41a24..97205cb44 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt @@ -100,6 +100,9 @@ private val ACCENT_PRIMARY_COLORS = arrayOf( /** * The data object for an accent. In the UI this is known as a "Color Scheme." + * This can be nominally used to gleam some attributes about a given color scheme, but this + * is not recommended. Attributes are usually the better option in nearly all cases. + * * @property name The name of this accent * @property theme The theme resource for this accent * @property blackTheme The black theme resource for this accent @@ -111,36 +114,4 @@ data class Accent(val index: Int) { val theme: Int get() = ACCENT_THEMES[index] val blackTheme: Int get() = ACCENT_BLACK_THEMES[index] val primary: Int get() = ACCENT_PRIMARY_COLORS[index] - - companion object { - @Volatile - private var CURRENT: Accent? = null - - /** - * Get the current accent. - * @return The current accent - * @throws IllegalStateException When the accent has not been set. - */ - fun get(): Accent { - val cur = CURRENT - - if (cur != null) { - return cur - } - - error("Accent must be set before retrieving it.") - } - - /** - * Set the current accent. - * @return The new accent - */ - fun set(accent: Accent): Accent { - synchronized(this) { - CURRENT = accent - } - - return accent - } - } } diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt index f42ecb3c9..0eaa56242 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt @@ -77,12 +77,10 @@ class AccentAdapter( val context = binding.accent.context binding.accent.isEnabled = !isSelected - binding.accent.imageTintList = if (isSelected) { // Switch out the currently selected ViewHolder with this one. selectedViewHolder?.setSelected(false) selectedViewHolder = this - context.getAttrColorSafe(R.attr.colorSurface).stateList } else { context.getColorSafe(android.R.color.transparent).stateList diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AccentDialog.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt similarity index 90% rename from app/src/main/java/org/oxycblt/auxio/accent/AccentDialog.kt rename to app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt index 3a6df6535..6c2321e5a 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/AccentDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt @@ -34,9 +34,9 @@ import org.oxycblt.auxio.util.logD * Dialog responsible for showing the list of accents to select. * @author OxygenCobalt */ -class AccentDialog : LifecycleDialog() { +class AccentCustomizeDialog : LifecycleDialog() { private val settingsManager = SettingsManager.getInstance() - private var pendingAccent = Accent.get() + private var pendingAccent = settingsManager.accent override fun onCreateView( inflater: LayoutInflater, @@ -53,18 +53,18 @@ class AccentDialog : LifecycleDialog() { binding.accentRecycler.apply { adapter = AccentAdapter(pendingAccent) { accent -> + logD("Switching selected accent to $accent") pendingAccent = accent } } - logD("Dialog created.") + logD("Dialog created") return binding.root } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putInt(KEY_PENDING_ACCENT, pendingAccent.index) } @@ -72,9 +72,9 @@ class AccentDialog : LifecycleDialog() { builder.setTitle(R.string.set_accent) builder.setPositiveButton(android.R.string.ok) { _, _ -> - if (pendingAccent != Accent.get()) { + if (pendingAccent != settingsManager.accent) { + logD("Applying new accent") settingsManager.accent = pendingAccent - requireActivity().recreate() } diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt rename to app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt index 4d371927e..48ed253ee 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt @@ -30,7 +30,7 @@ import kotlin.math.max * of the RecyclerView. * Adapted from this StackOverflow answer: https://stackoverflow.com/a/30256880/14143986 */ -class AutoGridLayoutManager( +class AccentGridLayoutManager( context: Context, attrs: AttributeSet, defStyleAttr: Int, diff --git a/app/src/main/java/org/oxycblt/auxio/coil/AuxioFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/AuxioFetcher.kt index 910e4c43c..266227ae2 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/AuxioFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/AuxioFetcher.kt @@ -27,6 +27,7 @@ import okio.source import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.logW import java.io.ByteArrayInputStream import java.io.InputStream import android.util.Size as AndroidSize @@ -55,6 +56,7 @@ abstract class AuxioFetcher : Fetcher { fetchMediaStoreCovers(context, album) } } catch (e: Exception) { + logW("Unable to extract album art due to an error") null } } @@ -80,7 +82,6 @@ abstract class AuxioFetcher : Fetcher { // music app which relies on proprietary OneUI extensions instead of AOSP. That means // we have to have another layer of redundancy to retain quality. Thanks samsung. Prick. val result = fetchAospMetadataCovers(context, album) - if (result != null) { return result } @@ -88,7 +89,6 @@ abstract class AuxioFetcher : Fetcher { // Our next fallback is to rely on ExoPlayer's largely half-baked and undocumented // metadata system. val exoResult = fetchExoplayerCover(context, album) - if (exoResult != null) { return exoResult } @@ -97,7 +97,6 @@ abstract class AuxioFetcher : Fetcher { // going against the point of this setting. The previous two calls are just too unreliable // and we can't do any filesystem traversing due to scoped storage. val mediaStoreResult = fetchMediaStoreCovers(context, album) - if (mediaStoreResult != null) { return mediaStoreResult } @@ -192,7 +191,7 @@ abstract class AuxioFetcher : Fetcher { } else if (stream != null) { // In the case a front cover is not found, use the first image in the tag instead. // This can be corrected later on if a front cover frame is found. - logD("No front cover image, using image of type $type instead") + logW("No front cover image, using image of type $type instead") stream = ByteArrayInputStream(pic) } @@ -223,9 +222,10 @@ abstract class AuxioFetcher : Fetcher { val increment = AndroidSize(mosaicSize.width / 2, mosaicSize.height / 2) val mosaicBitmap = Bitmap.createBitmap( - mosaicSize.width, mosaicSize.height, Bitmap.Config.ARGB_8888 + mosaicSize.width, + mosaicSize.height, + Bitmap.Config.ARGB_8888 ) - val canvas = Canvas(mosaicBitmap) var x = 0 diff --git a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt index 418083170..f3119c7b7 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt @@ -79,7 +79,6 @@ class ArtistImageFetcher private constructor( override suspend fun fetch(): FetchResult? { val albums = Sort.ByName(true) .sortAlbums(artist.albums) - val results = albums.mapAtMost(4) { album -> fetchArt(context, album) } 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 689f8113d..18dcc05f3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -40,6 +40,7 @@ import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.util.canScroll import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.showToast /** @@ -111,10 +112,11 @@ class AlbumDetailFragment : DetailFragment() { // fragment should be launched otherwise. is Song -> { if (detailModel.curAlbum.value!!.id == item.album.id) { + logD("Navigating to a song in this album") scrollToItem(item.id, detailAdapter) - detailModel.finishNavToItem() } else { + logD("Navigating to another album") findNavController().navigate( AlbumDetailFragmentDirections.actionShowAlbum(item.album.id) ) @@ -125,9 +127,11 @@ class AlbumDetailFragment : DetailFragment() { // detail fragment. is Album -> { if (detailModel.curAlbum.value!!.id == item.id) { + logD("Navigating to the top of this album") binding.detailRecycler.scrollToPosition(0) detailModel.finishNavToItem() } else { + logD("Navigating to another album") findNavController().navigate( AlbumDetailFragmentDirections.actionShowAlbum(item.id) ) @@ -136,13 +140,14 @@ class AlbumDetailFragment : DetailFragment() { // Always launch a new ArtistDetailFragment. is Artist -> { + logD("Navigating to another artist") findNavController().navigate( AlbumDetailFragmentDirections.actionShowArtist(item.id) ) } - else -> { - } + null -> {} + else -> logW("Unsupported navigation item ${item::class.java}") } } @@ -161,7 +166,7 @@ class AlbumDetailFragment : DetailFragment() { } } - logD("Fragment created.") + logD("Fragment created") return binding.root } 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 eaa875e0b..60f112b4c 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -35,6 +35,7 @@ import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.logW /** * The [DetailFragment] for an artist. @@ -98,25 +99,33 @@ class ArtistDetailFragment : DetailFragment() { when (item) { is Artist -> { if (item.id == detailModel.curArtist.value?.id) { + logD("Navigating to the top of this artist") binding.detailRecycler.scrollToPosition(0) detailModel.finishNavToItem() } else { + logD("Navigating to another artist") findNavController().navigate( ArtistDetailFragmentDirections.actionShowArtist(item.id) ) } } - is Album -> findNavController().navigate( - ArtistDetailFragmentDirections.actionShowAlbum(item.id) - ) - - is Song -> findNavController().navigate( - ArtistDetailFragmentDirections.actionShowAlbum(item.album.id) - ) - - else -> { + is Album -> { + logD("Navigating to another album") + findNavController().navigate( + ArtistDetailFragmentDirections.actionShowAlbum(item.id) + ) } + + is Song -> { + logD("Navigating to another album") + findNavController().navigate( + ArtistDetailFragmentDirections.actionShowAlbum(item.album.id) + ) + } + + null -> {} + else -> logW("Unsupported navigation item ${item::class.java}") } } @@ -141,7 +150,7 @@ class ArtistDetailFragment : DetailFragment() { } } - logD("Fragment created.") + logD("Fragment created") return binding.root } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt index 516eff0d6..6a073aff6 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt @@ -14,6 +14,9 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.appbar.AppBarLayout import org.oxycblt.auxio.R import org.oxycblt.auxio.ui.EdgeAppBarLayout +import org.oxycblt.auxio.util.logE +import org.oxycblt.auxio.util.logTraceOrThrow +import java.lang.Exception /** * An [EdgeAppBarLayout] variant that also shows the name of the toolbar whenever the detail @@ -39,9 +42,8 @@ class DetailAppBarLayout @JvmOverloads constructor( (layoutParams as CoordinatorLayout.LayoutParams).behavior = Behavior(context) } - private fun findTitleView(): AppCompatTextView { + private fun findTitleView(): AppCompatTextView? { val titleView = mTitleView - if (titleView != null) { return titleView } @@ -49,13 +51,18 @@ class DetailAppBarLayout @JvmOverloads constructor( val toolbar = findViewById(R.id.detail_toolbar) // Reflect to get the actual title view to do transformations on - val newTitleView = Toolbar::class.java.getDeclaredField("mTitleTextView").run { - isAccessible = true - get(toolbar) as AppCompatTextView + val newTitleView = try { + Toolbar::class.java.getDeclaredField("mTitleTextView").run { + isAccessible = true + get(toolbar) as AppCompatTextView + } + } catch (e: Exception) { + logE("Could not get toolbar title view (likely an internal code change)") + e.logTraceOrThrow() + return null } newTitleView.alpha = 0f - mTitleView = newTitleView return newTitleView } @@ -95,11 +102,11 @@ class DetailAppBarLayout @JvmOverloads constructor( to = 0f } - if (titleView.alpha == to) return + if (titleView?.alpha == to) return mTitleAnimator = ValueAnimator.ofFloat(from, to).apply { addUpdateListener { - titleView.alpha = it.animatedValue as Float + titleView?.alpha = it.animatedValue as Float } duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong() 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 b085c946f..a29a934f3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -31,6 +31,7 @@ import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.memberBinding import org.oxycblt.auxio.util.applySpans +import org.oxycblt.auxio.util.logD /** * A Base [Fragment] implementing the base features shared across all detail fragments. @@ -43,13 +44,11 @@ abstract class DetailFragment : Fragment() { override fun onResume() { super.onResume() - detailModel.setNavigating(false) } override fun onStop() { super.onStop() - // Cancel all pending menus when this fragment stops to prevent bugs/crashes detailModel.finishShowMenu(null) } @@ -94,7 +93,6 @@ abstract class DetailFragment : Fragment() { binding.detailRecycler.apply { adapter = detailAdapter setHasFixedSize(true) - applySpans(gridLookup) } } @@ -105,6 +103,8 @@ abstract class DetailFragment : Fragment() { * @param showItem Which menu items to keep */ protected fun showMenu(config: DetailViewModel.MenuConfig, showItem: ((Int) -> Boolean)? = null) { + logD("Launching menu [$config]") + PopupMenu(config.anchor.context, config.anchor).apply { inflate(R.menu.menu_detail_sort) 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 3c1db8a1b..fed77ca96 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Sort +import org.oxycblt.auxio.util.logD /** * ViewModel that stores data for the [DetailFragment]s. This includes: @@ -77,12 +78,10 @@ class DetailViewModel : ViewModel() { private set private var currentMenuContext: DisplayMode? = null - private val settingsManager = SettingsManager.getInstance() fun setGenre(id: Long) { if (mCurGenre.value?.id == id) return - val musicStore = MusicStore.requireInstance() mCurGenre.value = musicStore.genres.find { it.id == id } refreshGenreData() @@ -90,7 +89,6 @@ class DetailViewModel : ViewModel() { fun setArtist(id: Long) { if (mCurArtist.value?.id == id) return - val musicStore = MusicStore.requireInstance() mCurArtist.value = musicStore.artists.find { it.id == id } refreshArtistData() @@ -98,7 +96,6 @@ class DetailViewModel : ViewModel() { fun setAlbum(id: Long) { if (mCurAlbum.value?.id == id) return - val musicStore = MusicStore.requireInstance() mCurAlbum.value = musicStore.albums.find { it.id == id } refreshAlbumData() @@ -112,6 +109,7 @@ class DetailViewModel : ViewModel() { mShowMenu.value = null if (newMode != null) { + logD("Applying new sort mode") when (currentMenuContext) { DisplayMode.SHOW_ALBUMS -> { settingsManager.detailAlbumSort = newMode @@ -154,7 +152,9 @@ class DetailViewModel : ViewModel() { } private fun refreshGenreData() { - val data = mutableListOf(curGenre.value!!) + logD("Refreshing genre data") + val genre = requireNotNull(curGenre.value) + val data = mutableListOf(genre) data.add( ActionHeader( @@ -175,7 +175,8 @@ class DetailViewModel : ViewModel() { } private fun refreshArtistData() { - val artist = curArtist.value!! + logD("Refreshing artist data") + val artist = requireNotNull(curArtist.value) val data = mutableListOf(artist) data.add( @@ -206,7 +207,9 @@ class DetailViewModel : ViewModel() { } private fun refreshAlbumData() { - val data = mutableListOf(curAlbum.value!!) + logD("Refreshing album data") + val album = requireNotNull(curAlbum.value) + val data = mutableListOf(album) data.add( ActionHeader( 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 8bb6d9ac9..a14d4378e 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -35,6 +35,7 @@ import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.logW /** * The [DetailFragment] for a genre. @@ -79,20 +80,29 @@ class GenreDetailFragment : DetailFragment() { detailModel.navToItem.observe(viewLifecycleOwner) { item -> when (item) { // All items will launch new detail fragments. - is Artist -> findNavController().navigate( - GenreDetailFragmentDirections.actionShowArtist(item.id) - ) - - is Album -> findNavController().navigate( - GenreDetailFragmentDirections.actionShowAlbum(item.id) - ) - - is Song -> findNavController().navigate( - GenreDetailFragmentDirections.actionShowAlbum(item.album.id) - ) - - else -> { + is Artist -> { + logD("Navigating to another artist") + findNavController().navigate( + GenreDetailFragmentDirections.actionShowArtist(item.id) + ) } + + is Album -> { + logD("Navigating to another album") + findNavController().navigate( + GenreDetailFragmentDirections.actionShowAlbum(item.id) + ) + } + + is Song -> { + logD("Navigating to another song") + findNavController().navigate( + GenreDetailFragmentDirections.actionShowAlbum(item.album.id) + ) + } + + null -> {} + else -> logW("Unsupported navigation command ${item::class.java}") } } @@ -115,7 +125,7 @@ class GenreDetailFragment : DetailFragment() { } } - logD("Fragment created.") + logD("Fragment created") return binding.root } 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 c48a069d3..f09acf501 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 @@ -58,7 +58,6 @@ class AlbumDetailAdapter( is Album -> ALBUM_DETAIL_ITEM_TYPE is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE is Song -> ALBUM_SONG_ITEM_TYPE - else -> -1 } } @@ -86,7 +85,6 @@ class AlbumDetailAdapter( is Album -> (holder as AlbumDetailViewHolder).bind(item) is Song -> (holder as AlbumSongViewHolder).bind(item) is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item) - else -> { } } @@ -127,7 +125,6 @@ class AlbumDetailAdapter( recycler.layoutManager?.findViewByPosition(pos)?.let { child -> recycler.getChildViewHolder(child)?.let { currentHolder = it as Highlightable - currentHolder?.setHighlighted(true) } } 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 de8c0a32c..614522ca7 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 @@ -33,12 +33,12 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.bindArtistInfo import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.ActionHeaderViewHolder import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.HeaderViewHolder -import org.oxycblt.auxio.util.getPluralSafe import org.oxycblt.auxio.util.inflater /** @@ -64,7 +64,6 @@ class ArtistDetailAdapter( is Song -> ARTIST_SONG_ITEM_TYPE is Header -> HeaderViewHolder.ITEM_TYPE is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE - else -> -1 } } @@ -174,7 +173,6 @@ class ArtistDetailAdapter( recycler.layoutManager?.findViewByPosition(pos)?.let { child -> recycler.getChildViewHolder(child)?.let { currentSongHolder = it as Highlightable - currentSongHolder?.setHighlighted(true) } } @@ -205,11 +203,7 @@ class ArtistDetailAdapter( .entries.maxByOrNull { it.value.size } ?.key ?: context.getString(R.string.def_genre) - binding.detailInfo.text = context.getString( - R.string.fmt_counts, - context.getPluralSafe(R.plurals.fmt_album_count, data.albums.size), - context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size) - ) + binding.detailInfo.bindArtistInfo(data) binding.detailPlayButton.setOnClickListener { playbackModel.playArtist(data, false) 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 9f0e8366c..804b33561 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 @@ -30,11 +30,11 @@ import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.bindGenreInfo import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.ActionHeaderViewHolder import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.DiffCallback -import org.oxycblt.auxio.util.getPluralSafe import org.oxycblt.auxio.util.inflater /** @@ -54,7 +54,6 @@ class GenreDetailAdapter( is Genre -> GENRE_DETAIL_ITEM_TYPE is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE is Song -> GENRE_SONG_ITEM_TYPE - else -> -1 } } @@ -121,7 +120,6 @@ class GenreDetailAdapter( recycler.layoutManager?.findViewByPosition(pos)?.let { child -> recycler.getChildViewHolder(child)?.let { currentHolder = it as Highlightable - currentHolder?.setHighlighted(true) } } @@ -143,11 +141,7 @@ class GenreDetailAdapter( } binding.detailName.text = data.resolvedName - - binding.detailSubhead.apply { - text = context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size) - } - + binding.detailSubhead.bindGenreInfo(data) binding.detailInfo.text = data.totalDuration binding.detailPlayButton.setOnClickListener { diff --git a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt index a5d434f95..b3562d563 100644 --- a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt @@ -55,7 +55,6 @@ class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu writableDatabase.transaction { delete(TABLE_NAME, null, null) - logD("Deleted paths db") for (path in paths) { @@ -66,6 +65,8 @@ class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu } ) } + + logD("Successfully wrote ${paths.size} paths to db") } } @@ -76,17 +77,20 @@ class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu assertBackgroundThread() val paths = mutableListOf() - readableDatabase.queryAll(TABLE_NAME) { cursor -> while (cursor.moveToNext()) { paths.add(cursor.getString(0)) } } + logD("Successfully read ${paths.size} paths from db") + return paths } companion object { + // Blacklist is still used here for compatibility reasons, please don't get + // your pants in a twist about it. const val DB_VERSION = 1 const val DB_NAME = "auxio_blacklist_database.db" diff --git a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt index bb339023d..c5b9be463 100644 --- a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt @@ -77,13 +77,16 @@ class ExcludedDialog : LifecycleDialog() { dialog.setOnShowListener { dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener { + logD("Opening launcher") launcher.launch(null) } dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener { if (excludedModel.isModified) { + logD("Committing changes") saveAndRestart() } else { + logD("Dropping changes") dismiss() } } @@ -93,11 +96,10 @@ class ExcludedDialog : LifecycleDialog() { excludedModel.paths.observe(viewLifecycleOwner) { paths -> adapter.submitList(paths) - binding.excludedEmpty.isVisible = paths.isEmpty() } - logD("Dialog created.") + logD("Dialog created") return binding.root } @@ -114,6 +116,7 @@ class ExcludedDialog : LifecycleDialog() { private fun addDocTreePath(uri: Uri?) { // A null URI means that the user left the file picker without picking a directory if (uri == null) { + logD("No URI given (user closed the dialog)") return } @@ -142,6 +145,7 @@ class ExcludedDialog : LifecycleDialog() { return getRootPath() + "/" + typeAndPath.last() } + logD("Unsupported volume ${typeAndPath[0]}") return null } @@ -156,7 +160,6 @@ class ExcludedDialog : LifecycleDialog() { /** * Get *just* the root path, nothing else is really needed. */ - @Suppress("DEPRECATION") private fun getRootPath(): String { return Environment.getExternalStorageDirectory().absolutePath } diff --git a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt index 504cb4f73..8de9ccc9b 100644 --- a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.oxycblt.auxio.util.logD /** * ViewModel that acts as a wrapper around [ExcludedDatabase], allowing for the addition/removal @@ -73,10 +74,13 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo */ fun save(onDone: () -> Unit) { viewModelScope.launch(Dispatchers.IO) { + val start = System.currentTimeMillis() excludedDatabase.writePaths(mPaths.value!!) dbPaths = mPaths.value!! - onDone() + this@ExcludedViewModel.logD( + "Path save completed successfully in ${System.currentTimeMillis() - start}ms" + ) } } @@ -85,11 +89,14 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo */ private fun loadDatabasePaths() { viewModelScope.launch(Dispatchers.IO) { + val start = System.currentTimeMillis() dbPaths = excludedDatabase.readPaths() - withContext(Dispatchers.Main) { mPaths.value = dbPaths.toMutableList() } + this@ExcludedViewModel.logD( + "Path load completed successfully in ${System.currentTimeMillis() - start}ms" + ) } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt index 434820910..f6b405332 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.AttributeSet import com.google.android.material.floatingactionbutton.FloatingActionButton import org.oxycblt.auxio.util.getDimenSizeSafe +import org.oxycblt.auxio.util.logD import com.google.android.material.R as MaterialR /** @@ -20,7 +21,10 @@ class AdaptiveFloatingActionButton @JvmOverloads constructor( init { size = SIZE_NORMAL + // Use a large FAB on large screens, as it makes it easier to touch. if (resources.configuration.smallestScreenWidthDp >= 640) { + logD("Using large FAB configuration") + val largeFabSize = context.getDimenSizeSafe( MaterialR.dimen.m3_large_fab_size ) @@ -29,7 +33,6 @@ class AdaptiveFloatingActionButton @JvmOverloads constructor( MaterialR.dimen.m3_large_fab_max_image_size ) - // Use a large FAB on large screens, as it makes it easier to touch. customSize = largeFabSize setMaxImageSize(largeImageSize) } diff --git a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt index d81da4d20..e76f5941a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt @@ -3,6 +3,7 @@ package org.oxycblt.auxio.home import android.content.Context import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator +import org.oxycblt.auxio.util.logD /** * A tag configuration strategy that automatically adapts the tab layout to the screen size. @@ -20,15 +21,22 @@ class AdaptiveTabStrategy( val tabMode = homeModel.tabs[position] when { - width < 370 -> + width < 370 -> { + logD("Using icon-only configuration") tab.setIcon(tabMode.icon) .setContentDescription(tabMode.string) + } - width < 640 -> tab.setText(tabMode.string) + width < 640 -> { + logD("Using text-only configuration") + tab.setText(tabMode.string) + } - else -> + else -> { + logD("Using icon-and-text configuration") tab.setIcon(tabMode.icon) .setText(tabMode.string) + } } } } 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 7143169f9..b5b447ad4 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -49,6 +49,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE +import org.oxycblt.auxio.util.logTraceOrThrow /** * The main "Launching Point" fragment of Auxio, allowing navigation to the detail @@ -77,16 +78,19 @@ class HomeFragment : Fragment() { setOnMenuItemClickListener { item -> when (item.itemId) { R.id.action_search -> { + logD("Navigating to search") findNavController().navigate(HomeFragmentDirections.actionShowSearch()) } R.id.action_settings -> { + logD("Navigating to settings") parentFragment?.parentFragment?.findNavController()?.navigate( MainFragmentDirections.actionShowSettings() ) } R.id.action_about -> { + logD("Navigating to about") parentFragment?.parentFragment?.findNavController()?.navigate( MainFragmentDirections.actionShowAbout() ) @@ -96,20 +100,16 @@ class HomeFragment : Fragment() { R.id.option_sort_asc -> { item.isChecked = !item.isChecked - val new = homeModel.getSortForDisplay(homeModel.curTab.value!!) .ascending(item.isChecked) - homeModel.updateCurrentSort(new) } // Sorting option was selected, mark it as selected and update the mode else -> { item.isChecked = true - val new = homeModel.getSortForDisplay(homeModel.curTab.value!!) .assignId(item.itemId) - homeModel.updateCurrentSort(requireNotNull(new)) } } @@ -141,8 +141,8 @@ class HomeFragment : Fragment() { set(recycler, slop * 3) // 3x seems to be the best fit here } } catch (e: Exception) { - logE("Unable to reduce ViewPager sensitivity") - logE(e.stackTraceToString()) + logE("Unable to reduce ViewPager sensitivity (likely an internal code change)") + e.logTraceOrThrow() } // We know that there will only be a fixed amount of tabs, so we manually set this @@ -174,7 +174,7 @@ class HomeFragment : Fragment() { is MusicStore.Response.Ok -> binding.homeFab.show() // While loading or during an error, make sure we keep the shuffle fab hidden so - // that any kind of loading is impossible. PlaybackStateManager also relies on this + // that any kind of playback is impossible. PlaybackStateManager also relies on this // invariant, so please don't change it. else -> binding.homeFab.hide() } @@ -207,7 +207,7 @@ class HomeFragment : Fragment() { homeModel.curTab.observe(viewLifecycleOwner) { t -> val tab = requireNotNull(t) - // Make sure that we update the scrolling view and allowed menu items before whenever + // Make sure that we update the scrolling view and allowed menu items whenever // the tab changes. when (tab) { DisplayMode.SHOW_SONGS -> updateSortMenu(sortItem, tab) @@ -229,8 +229,9 @@ class HomeFragment : Fragment() { } detailModel.navToItem.observe(viewLifecycleOwner) { item -> - // The AppBarLayout gets confused and collapses when we navigate too fast, wait for it - // to draw before we continue. + // The AppBarLayout gets confused when we navigate too fast, wait for it to draw + // before we navigate. + // This is only here just in case a collapsing toolbar is re-added. binding.homeAppbar.post { when (item) { is Song -> findNavController().navigate( @@ -255,7 +256,7 @@ class HomeFragment : Fragment() { } } - logD("Fragment Created.") + logD("Fragment Created") return binding.root } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 0765d3ee1..9089b23f4 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -32,6 +32,7 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Sort +import org.oxycblt.auxio.util.logD /** * The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state. @@ -78,7 +79,6 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback { viewModelScope.launch { val musicStore = MusicStore.awaitInstance() - mSongs.value = settingsManager.libSongSort.sortSongs(musicStore.songs) mAlbums.value = settingsManager.libAlbumSort.sortAlbums(musicStore.albums) mArtists.value = settingsManager.libArtistSort.sortParents(musicStore.artists) @@ -90,6 +90,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback { * Update the current tab based off of the new ViewPager position. */ fun updateCurrentTab(pos: Int) { + logD("Updating current tab to ${tabs[pos]}") mCurTab.value = tabs[pos] } @@ -110,6 +111,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback { * Update the currently displayed item's [Sort]. */ fun updateCurrentSort(sort: Sort) { + logD("Updating ${mCurTab.value} sort to $sort") when (mCurTab.value) { DisplayMode.SHOW_SONGS -> { settingsManager.libSongSort = sort 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 a4166a1ec..3f8a30686 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 @@ -109,7 +109,7 @@ sealed class Tab(open val mode: DisplayMode) { // For safety, return null if we have an empty or larger-than-expected tab array. if (distinct.isEmpty() || distinct.size < SEQUENCE_LEN) { - logE("Sequence size was ${distinct.size}, which is invalid.") + logE("Sequence size was ${distinct.size}, which is invalid") return null } diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt index 5dfa61a1f..93b478f7d 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt @@ -29,6 +29,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogTabsBinding import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.ui.LifecycleDialog +import org.oxycblt.auxio.util.logD /** * The dialog for customizing library tabs. This dialog does not rely on any specific ViewModel @@ -49,7 +50,6 @@ class TabCustomizeDialog : LifecycleDialog() { if (savedInstanceState != null) { // Restore any pending tab configurations val tabs = Tab.fromSequence(savedInstanceState.getInt(KEY_TABS)) - if (tabs != null) { pendingTabs = tabs } @@ -66,10 +66,9 @@ class TabCustomizeDialog : LifecycleDialog() { // of how ViewHolders are bound], but instead simply look for the mode in // the list of pending tabs and update that instead. val index = pendingTabs.indexOfFirst { it.mode == tab.mode } - if (index != -1) { val curTab = pendingTabs[index] - + logD("Updating tab $curTab to $tab") pendingTabs[index] = when (curTab) { is Tab.Visible -> Tab.Invisible(curTab.mode) is Tab.Invisible -> Tab.Visible(curTab.mode) @@ -93,7 +92,6 @@ class TabCustomizeDialog : LifecycleDialog() { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putInt(KEY_TABS, Tab.toSequence(pendingTabs)) } @@ -101,6 +99,7 @@ class TabCustomizeDialog : LifecycleDialog() { builder.setTitle(R.string.set_lib_tabs) builder.setPositiveButton(android.R.string.ok) { _, _ -> + logD("Committing tab changes") settingsManager.libTabs = pendingTabs } diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index 8f35bf4ac..d4f7e296c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -124,8 +124,12 @@ data class Song( val internalGroupingArtistName: String get() = internalMediaStoreAlbumArtistName ?: internalMediaStoreArtistName ?: MediaStore.UNKNOWN_STRING + /** Internal field. Do not use. */ + val internalIsMissingAlbum: Boolean get() = mAlbum == null + /** Internal field. Do not use. */ + val internalIsMissingArtist: Boolean get() = mAlbum?.internalIsMissingArtist ?: true /** Internal field. Do not use. **/ - val internalMissingGenre: Boolean get() = mGenre == null + val internalIsMissingGenre: Boolean get() = mGenre == null /** Internal method. Do not use. */ fun internalLinkAlbum(album: Album) { @@ -180,6 +184,9 @@ data class Album( val resolvedArtistName: String get() = artist.resolvedName + /** Internal field. Do not use. */ + val internalIsMissingArtist: Boolean = mArtist != null + /** Internal method. Do not use. */ fun internalLinkArtist(artist: Artist) { mArtist = artist diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index c39f267a5..286cb13b1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -7,8 +7,8 @@ import android.provider.MediaStore import androidx.core.database.getStringOrNull import org.oxycblt.auxio.R import org.oxycblt.auxio.excluded.ExcludedDatabase +import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE -import java.lang.Exception /** * This class acts as the base for most the black magic required to get a remotely sensible music @@ -26,7 +26,7 @@ import java.lang.Exception * have to query for each genre, query all the songs in each genre, and then iterate through those * songs to link every song with their genre. This is not documented anywhere, and the * O(mom im scared) algorithm you have to run to get it working single-handedly DOUBLES Auxio's - * loading times. At no point have the devs considered that this column is absolutely insane, and + * loading times. At no point have the devs considered that this system is absolutely insane, and * instead focused on adding infuriat- I mean nice proprietary extensions to MediaStore for their * own Google Play Music, and of course every Google Play Music user knew how great that turned * out! @@ -88,14 +88,14 @@ class MusicLoader { val artists = buildArtists(context, albums) val genres = readGenres(context, songs) - // Sanity check: Ensure that all songs are well-formed. + // Sanity check: Ensure that all songs are linked up to albums/artists/genres. for (song in songs) { - try { - song.album.artist - song.genre - } catch (e: Exception) { + if (song.internalIsMissingAlbum || + song.internalIsMissingArtist || + song.internalIsMissingGenre + ) { logE("Found malformed song: ${song.name}") - throw e + throw IllegalStateException() } } @@ -204,6 +204,8 @@ class MusicLoader { it.internalMediaStoreAlbumArtistName to it.track to it.duration }.toMutableList() + logD("Successfully loaded ${songs.size} songs") + return songs } @@ -247,6 +249,8 @@ class MusicLoader { ) } + logD("Successfully built ${albums.size} albums") + return albums } @@ -264,14 +268,15 @@ class MusicLoader { // Album deduplication does not eliminate every case of fragmented artists, do // we deduplicate in the artist creation step as well. - // Note that we actually don't do this in groupBy. This is generally because we - // only want to default to a lowercase artist name when we have no other choice. + // Note that we actually don't do this in groupBy. This is generally because using + // a template song may not result in the best possible artist name in all cases. val previousArtistIndex = artists.indexOfFirst { artist -> artist.name.lowercase() == artistName.lowercase() } if (previousArtistIndex > -1) { val previousArtist = artists[previousArtistIndex] + logD("Merging duplicate artist into pre-existing artist ${previousArtist.name}") artists[previousArtistIndex] = Artist( previousArtist.name, previousArtist.resolvedName, @@ -288,6 +293,8 @@ class MusicLoader { } } + logD("Successfully built ${artists.size} artists") + return artists } @@ -327,7 +334,7 @@ class MusicLoader { } } - val songsWithoutGenres = songs.filter { it.internalMissingGenre } + val songsWithoutGenres = songs.filter { it.internalIsMissingGenre } if (songsWithoutGenres.isNotEmpty()) { // Songs that don't have a genre will be thrown into an unknown genre. @@ -340,6 +347,8 @@ class MusicLoader { genres.add(unknownGenre) } + logD("Successfully loaded ${genres.size} genres") + return genres } 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 22addc747..7cc5782bc 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -55,7 +55,7 @@ class MusicStore private constructor() { * Load/Sort the entire music library. Should always be ran on a coroutine. */ private fun load(context: Context): Response { - logD("Starting initial music load...") + logD("Starting initial music load") val notGranted = ContextCompat.checkSelfPermission( context, Manifest.permission.READ_EXTERNAL_STORAGE @@ -76,11 +76,10 @@ class MusicStore private constructor() { mArtists = library.artists mGenres = library.genres - logD("Music load completed successfully in ${System.currentTimeMillis() - start}ms.") + logD("Music load completed successfully in ${System.currentTimeMillis() - start}ms") } catch (e: Exception) { - logE("Something went horribly wrong.") + logE("Something went horribly wrong") logE(e.stackTraceToString()) - return Response.Err(ErrorKind.FAILED) } @@ -117,6 +116,7 @@ class MusicStore private constructor() { /** * A response that [MusicStore] returns when loading music. * And before you ask, yes, I do like rust. + * TODO: Replace this with the kotlin builtin */ sealed class Response { class Ok(val musicStore: MusicStore) : Response() @@ -201,7 +201,7 @@ class MusicStore private constructor() { */ fun requireInstance(): MusicStore { return requireNotNull(maybeGetInstance()) { - "Required MusicStore instance was not available." + "Required MusicStore instance was not available" } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt index cc3d87b49..0dc9c2d29 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt @@ -25,6 +25,8 @@ import androidx.core.text.isDigitsOnly import androidx.databinding.BindingAdapter import org.oxycblt.auxio.R import org.oxycblt.auxio.util.getPluralSafe +import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.logW /** * A complete array of all the hardcoded genre values for ID3(v2), contains standard genres and @@ -98,6 +100,7 @@ fun String.getGenreNameCompat(): String? { */ fun Long.toDuration(isElapsed: Boolean): String { if (!isElapsed && this == 0L) { + logD("Non-elapsed duration is zero, using --:--") return "--:--" } @@ -121,14 +124,53 @@ fun Int.toDate(context: Context): String { // --- BINDING ADAPTERS --- -/** - * Bind the album + song counts for an artist - */ -@BindingAdapter("artistCounts") -fun TextView.bindArtistCounts(artist: Artist) { +@BindingAdapter("songInfo") +fun TextView.bindSongInfo(song: Song?) { + if (song == null) { + logW("Song was null, not applying info") + return + } + + text = context.getString( + R.string.fmt_two, + song.resolvedArtistName, + song.resolvedAlbumName + ) +} + +@BindingAdapter("albumInfo") +fun TextView.bindAlbumInfo(album: Album?) { + if (album == null) { + logW("Album was null, not applying info") + return + } + + text = context.getString( + R.string.fmt_two, album.resolvedArtistName, + context.getPluralSafe(R.plurals.fmt_song_count, album.songs.size) + ) +} + +@BindingAdapter("artistInfo") +fun TextView.bindArtistInfo(artist: Artist?) { + if (artist == null) { + logW("Artist was null, not applying info") + return + } + text = context.getString( R.string.fmt_counts, context.getPluralSafe(R.plurals.fmt_album_count, artist.albums.size), context.getPluralSafe(R.plurals.fmt_song_count, artist.songs.size) ) } + +@BindingAdapter("genreInfo") +fun TextView.bindGenreInfo(genre: Genre?) { + if (genre == null) { + logW("Genre was null, not applying info") + return + } + + text = context.getPluralSafe(R.plurals.fmt_song_count, genre.songs.size) +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt index a71e76c19..b91adb12f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch +import org.oxycblt.auxio.util.logD class MusicViewModel : ViewModel() { private val mLoaderResponse = MutableLiveData(null) @@ -37,6 +38,7 @@ class MusicViewModel : ViewModel() { */ fun loadMusic(context: Context) { if (mLoaderResponse.value != null || isBusy) { + logD("Loader is busy/already completed, not reloading") return } @@ -45,15 +47,14 @@ class MusicViewModel : ViewModel() { viewModelScope.launch { val result = MusicStore.initInstance(context) - - isBusy = false mLoaderResponse.value = result + isBusy = false } } fun reloadMusic(context: Context) { + logD("Reloading music library") mLoaderResponse.value = null - loadMusic(context) } } 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 92ae2cd93..b9864f665 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -92,7 +92,6 @@ class PlaybackFragment : Fragment() { // Make marquee of song title work binding.playbackSong.isSelected = true binding.playbackSeekBar.onConfirmListener = playbackModel::setPosition - binding.playbackPlayPause.post { binding.playbackPlayPause.stateListAnimator = null } @@ -101,11 +100,11 @@ class PlaybackFragment : Fragment() { playbackModel.song.observe(viewLifecycleOwner) { song -> if (song != null) { - logD("Updating song display to ${song.name}.") + logD("Updating song display to ${song.name}") binding.song = song binding.playbackSeekBar.setDuration(song.seconds) } else { - logD("No song is being played, leaving.") + logD("No song is being played, leaving") findNavController().navigateUp() } } @@ -152,7 +151,7 @@ class PlaybackFragment : Fragment() { } } - logD("Fragment Created.") + logD("Fragment Created") return binding.root } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt index 027ccbf00..f5d984841 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt @@ -27,6 +27,7 @@ import org.oxycblt.auxio.util.disableDropShadowCompat import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.getDimenSafe import org.oxycblt.auxio.util.getDrawableSafe +import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.pxOfDp import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat import org.oxycblt.auxio.util.stateList @@ -225,6 +226,8 @@ class PlaybackLayout @JvmOverloads constructor( } private fun applyState(state: PanelState) { + logD("Applying panel state $state") + // Dragging events are really complex and we don't want to mess up the state // while we are in one. if (state == panelState || panelState == PanelState.DRAGGING) { @@ -357,10 +360,8 @@ class PlaybackLayout @JvmOverloads constructor( // bottom navigation is consumed by a bar. To fix this, we modify the bottom insets // to reflect the presence of the panel [at least in it's collapsed state] playbackContainerView.dispatchApplyWindowInsets(insets) - lastInsets = insets applyContentWindowInsets() - return insets } @@ -370,7 +371,6 @@ class PlaybackLayout @JvmOverloads constructor( */ private fun applyContentWindowInsets() { val insets = lastInsets - if (insets != null) { contentView.dispatchApplyWindowInsets(adjustInsets(insets)) } @@ -386,8 +386,9 @@ class PlaybackLayout @JvmOverloads constructor( val bars = insets.systemBarInsetsCompat val consumedByPanel = computePanelTopPosition(panelOffset) - measuredHeight val adjustedBottomInset = (consumedByPanel + bars.bottom).coerceAtLeast(0) - - return insets.replaceSystemBarInsetsCompat(bars.left, bars.top, bars.right, adjustedBottomInset) + return insets.replaceSystemBarInsetsCompat( + bars.left, bars.top, bars.right, adjustedBottomInset + ) } override fun onSaveInstanceState(): Parcelable = Bundle().apply { @@ -586,6 +587,8 @@ class PlaybackLayout @JvmOverloads constructor( (computePanelTopPosition(0f) - topPosition).toFloat() / panelRange private fun smoothSlideTo(offset: Float) { + logD("Smooth sliding to $offset") + val okay = dragHelper.smoothSlideViewTo( playbackContainerView, playbackContainerView.left, computePanelTopPosition(offset) ) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt index f2e517cd0..274995005 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt @@ -29,6 +29,7 @@ import org.oxycblt.auxio.databinding.ViewSeekBarBinding import org.oxycblt.auxio.music.toDuration import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.inflater +import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.stateList /** @@ -73,6 +74,7 @@ class PlaybackSeekBar @JvmOverloads constructor( // - The duration of the song was so low as to be rounded to zero when converted // to seconds. // In either of these cases, the seekbar is more or less useless. Disable it. + logD("Duration is 0, entering disabled state") binding.seekBar.apply { valueTo = 1f isEnabled = false 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 a741adafe..1fcb41a35 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -111,7 +111,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { */ fun playAlbum(album: Album, shuffled: Boolean) { if (album.songs.isEmpty()) { - logE("Album is empty, Not playing.") + logE("Album is empty, Not playing") return } @@ -125,7 +125,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { */ fun playArtist(artist: Artist, shuffled: Boolean) { if (artist.songs.isEmpty()) { - logE("Artist is empty, Not playing.") + logE("Artist is empty, Not playing") return } @@ -139,7 +139,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { */ fun playGenre(genre: Genre, shuffled: Boolean) { if (genre.songs.isEmpty()) { - logE("Genre is empty, Not playing.") + logE("Genre is empty, Not playing") return } @@ -156,7 +156,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { if (playbackManager.isRestored && MusicStore.loaded()) { playWithUriInternal(uri, context) } else { - logD("Cant play this URI right now, waiting...") + logD("Cant play this URI right now, waiting") mIntentUri = uri } @@ -213,12 +213,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { * [apply] is called just before the change is committed so that the adapter can be updated. */ fun removeQueueDataItem(adapterIndex: Int, apply: () -> Unit) { - val adjusted = adapterIndex + (playbackManager.queue.size - mNextUp.value!!.size) - logD("$adjusted") - - if (adjusted in playbackManager.queue.indices) { + val index = adapterIndex + (playbackManager.queue.size - mNextUp.value!!.size) + if (index in playbackManager.queue.indices) { apply() - playbackManager.removeQueueItem(adjusted) + playbackManager.removeQueueItem(index) } } /** @@ -227,10 +225,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { */ fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int, apply: () -> Unit): Boolean { val delta = (playbackManager.queue.size - mNextUp.value!!.size) - val from = adapterFrom + delta val to = adapterTo + delta - if (from in playbackManager.queue.indices && to in playbackManager.queue.indices) { apply() playbackManager.moveQueueItems(from, to) @@ -332,7 +328,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { * [PlaybackStateManager] instance. */ private fun restorePlaybackState() { - logD("Attempting to restore playback state.") + logD("Attempting to restore playback state") onSongUpdate(playbackManager.song) onPositionUpdate(playbackManager.position) 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 d61af2e03..6c6970662 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 @@ -70,11 +70,9 @@ class QueueAdapter( QUEUE_SONG_ITEM_TYPE -> QueueSongViewHolder( ItemQueueSongBinding.inflate(parent.context.inflater) ) - HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context) ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context) - - else -> error("Invalid ViewHolder item type $viewType.") + else -> error("Invalid ViewHolder item type $viewType") } } @@ -83,8 +81,7 @@ class QueueAdapter( is Song -> (holder as QueueSongViewHolder).bind(item) is Header -> (holder as HeaderViewHolder).bind(item) is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item) - - else -> logE("Bad data given to QueueAdapter.") + else -> logE("Bad data given to QueueAdapter") } } @@ -95,7 +92,6 @@ class QueueAdapter( fun submitList(newData: MutableList) { if (data != newData) { data = newData - listDiffer.submitList(newData) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index 3b80f863e..3478f6a9e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -27,6 +27,7 @@ import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.R import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.util.getDimenSafe +import org.oxycblt.auxio.util.logD import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -89,9 +90,10 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc val holder = viewHolder as QueueAdapter.QueueSongViewHolder if (shouldLift && isCurrentlyActive && actionState == ItemTouchHelper.ACTION_STATE_DRAG) { + logD("Lifting queue item") + val bg = holder.bodyView.background as MaterialShapeDrawable val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_small) - holder.itemView.animate() .translationZ(elevation) .setDuration(100) @@ -127,8 +129,9 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc val holder = viewHolder as QueueAdapter.QueueSongViewHolder if (holder.itemView.translationZ != 0.0f) { - val bg = holder.bodyView.background as MaterialShapeDrawable + logD("Dropping queue item") + val bg = holder.bodyView.background as MaterialShapeDrawable holder.itemView.animate() .translationZ(0.0f) .setDuration(100) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index cc9d2bb27..8765c4d98 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -28,6 +28,7 @@ import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.ItemTouchHelper import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.util.logD /** * A [Fragment] that shows the queue and enables editing as well. @@ -77,9 +78,11 @@ class QueueFragment : Fragment() { } playbackModel.isShuffling.observe(viewLifecycleOwner) { isShuffling -> + // Try to prevent the queue adapter from going spastic during reshuffle events + // by just scrolling back to the top. if (isShuffling != lastShuffle) { + logD("Reshuffle event, scrolling to top") lastShuffle = isShuffling - binding.queueRecycler.scrollToPosition(0) } } 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 5ed2a2b35..3450096f3 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 @@ -48,10 +48,10 @@ class PlaybackStateDatabase(context: Context) : override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = nuke(db) private fun nuke(db: SQLiteDatabase) { + logD("Nuking database") db.apply { execSQL("DROP TABLE IF EXISTS $TABLE_NAME_STATE") execSQL("DROP TABLE IF EXISTS $TABLE_NAME_QUEUE") - onCreate(this) } } @@ -103,34 +103,6 @@ class PlaybackStateDatabase(context: Context) : // --- INTERFACE FUNCTIONS --- - /** - * Clear the previously written [SavedState] and write a new one. - */ - fun writeState(state: SavedState) { - assertBackgroundThread() - - writableDatabase.transaction { - delete(TABLE_NAME_STATE, null, null) - - this@PlaybackStateDatabase.logD("Wiped state db.") - - val stateData = ContentValues(10).apply { - put(StateColumns.COLUMN_ID, 0) - put(StateColumns.COLUMN_SONG_HASH, state.song?.id) - put(StateColumns.COLUMN_POSITION, state.position) - put(StateColumns.COLUMN_PARENT_HASH, state.parent?.id) - put(StateColumns.COLUMN_QUEUE_INDEX, state.queueIndex) - put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt()) - put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling) - put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt()) - } - - insert(TABLE_NAME_STATE, null, stateData) - } - - logD("Wrote state to database.") - } - /** * Read the stored [SavedState] from the database, if there is one. * @param musicStore Required to transform database songs/parents into actual instances @@ -178,11 +150,69 @@ class PlaybackStateDatabase(context: Context) : isShuffling = cursor.getInt(shuffleIndex) == 1, loopMode = LoopMode.fromInt(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE, ) + + logD("Successfully read playback state: $state") } return state } + /** + * Clear the previously written [SavedState] and write a new one. + */ + fun writeState(state: SavedState) { + assertBackgroundThread() + + writableDatabase.transaction { + delete(TABLE_NAME_STATE, null, null) + + this@PlaybackStateDatabase.logD("Wiped state db") + + val stateData = ContentValues(10).apply { + put(StateColumns.COLUMN_ID, 0) + put(StateColumns.COLUMN_SONG_HASH, state.song?.id) + put(StateColumns.COLUMN_POSITION, state.position) + put(StateColumns.COLUMN_PARENT_HASH, state.parent?.id) + put(StateColumns.COLUMN_QUEUE_INDEX, state.queueIndex) + put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt()) + put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling) + put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt()) + } + + insert(TABLE_NAME_STATE, null, stateData) + } + + logD("Wrote state to database") + } + + /** + * Read a list of queue items from this database. + * @param musicStore Required to transform database songs into actual song instances + */ + fun readQueue(musicStore: MusicStore): MutableList { + assertBackgroundThread() + + val queue = mutableListOf() + + readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor -> + if (cursor.count == 0) return@queryAll + + val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_HASH) + val albumIndex = cursor.getColumnIndexOrThrow(QueueColumns.ALBUM_HASH) + + while (cursor.moveToNext()) { + musicStore.findSongFast(cursor.getLong(songIndex), cursor.getLong(albumIndex)) + ?.let { song -> + queue.add(song) + } + } + } + + logD("Successfully read queue of ${queue.size} songs") + + return queue + } + /** * Write a queue to the database. */ @@ -190,12 +220,11 @@ class PlaybackStateDatabase(context: Context) : assertBackgroundThread() val database = writableDatabase - database.transaction { delete(TABLE_NAME_QUEUE, null, null) } - logD("Wiped queue db.") + logD("Wiped queue db") writeQueueBatch(queue, queue.size) } @@ -232,32 +261,6 @@ class PlaybackStateDatabase(context: Context) : } } - /** - * Read a list of queue items from this database. - * @param musicStore Required to transform database songs into actual song instances - */ - fun readQueue(musicStore: MusicStore): MutableList { - assertBackgroundThread() - - val queue = mutableListOf() - - readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor -> - if (cursor.count == 0) return@queryAll - - val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_HASH) - val albumIndex = cursor.getColumnIndexOrThrow(QueueColumns.ALBUM_HASH) - - while (cursor.moveToNext()) { - musicStore.findSongFast(cursor.getLong(songIndex), cursor.getLong(albumIndex)) - ?.let { song -> - queue.add(song) - } - } - } - - return queue - } - data class SavedState( val song: Song?, val position: Long, 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 fea3fd451..50ce3a643 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 @@ -224,7 +224,6 @@ class PlaybackStateManager private constructor() { private fun updatePlayback(song: Song, shouldPlay: Boolean = true) { mSong = song mPosition = 0 - setPlaying(shouldPlay) } @@ -271,18 +270,14 @@ class PlaybackStateManager private constructor() { * Remove a queue item at [index]. Will ignore invalid indexes. */ fun removeQueueItem(index: Int): Boolean { - logD("Removing item ${mQueue[index].name}.") - if (index > mQueue.size || index < 0) { - logE("Index is out of bounds, did not remove queue item.") - + logE("Index is out of bounds, did not remove queue item") return false } + logD("Removing item ${mQueue[index].name}") mQueue.removeAt(index) - pushQueueUpdate() - return true } @@ -292,15 +287,12 @@ class PlaybackStateManager private constructor() { fun moveQueueItems(from: Int, to: Int): Boolean { if (from > mQueue.size || from < 0 || to > mQueue.size || to < 0) { logE("Indices were out of bounds, did not move queue item") - return false } - val item = mQueue.removeAt(from) - mQueue.add(to, item) - + logD("Moving item $from to position $to") + mQueue.add(to, mQueue.removeAt(from)) pushQueueUpdate() - return true } @@ -501,7 +493,7 @@ class PlaybackStateManager private constructor() { * @param context [Context] required */ suspend fun saveStateToDatabase(context: Context) { - logD("Saving state to DB.") + logD("Saving state to DB") // Pack the entire state and save it to the database. withContext(Dispatchers.IO) { @@ -519,7 +511,7 @@ class PlaybackStateManager private constructor() { database.writeQueue(mQueue) this@PlaybackStateManager.logD( - "Save finished in ${System.currentTimeMillis() - start}ms" + "State save completed successfully in ${System.currentTimeMillis() - start}ms" ) } } @@ -529,10 +521,9 @@ class PlaybackStateManager private constructor() { * @param context [Context] required. */ suspend fun restoreFromDatabase(context: Context) { - logD("Getting state from DB.") + logD("Getting state from DB") val musicStore = MusicStore.maybeGetInstance() ?: return - val start: Long val playbackState: PlaybackStateDatabase.SavedState? val queue: MutableList @@ -549,15 +540,13 @@ class PlaybackStateManager private constructor() { // Get off the IO coroutine since it will cause LiveData updates to throw an exception if (playbackState != null) { - logD("Found playback state $playbackState") - unpackFromPlaybackState(playbackState) unpackQueue(queue) doParentSanityCheck() doIndexSanityCheck() } - logD("Restore finished in ${System.currentTimeMillis() - start}ms") + logD("State load completed successfully in ${System.currentTimeMillis() - start}ms") markRestored() } @@ -592,7 +581,7 @@ class PlaybackStateManager private constructor() { private fun doParentSanityCheck() { // Check if the parent was lost while in the DB. if (mSong != null && mParent == null && mPlaybackMode != PlaybackMode.ALL_SONGS) { - logD("Parent lost, attempting restore.") + logD("Parent lost, attempting restore") mParent = when (mPlaybackMode) { PlaybackMode.IN_ALBUM -> mQueue.firstOrNull()?.album diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt index d450a40c2..23b7c3a16 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt @@ -33,6 +33,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.util.getSystemServiceSafe import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.logW import kotlin.math.pow /** @@ -85,6 +86,7 @@ class AudioReactor( * Request the android system for audio focus */ fun requestFocus() { + logD("Requesting audio focus") AudioManagerCompat.requestAudioFocus(audioManager, request) } @@ -94,7 +96,7 @@ class AudioReactor( */ fun applyReplayGain(metadata: Metadata?) { if (metadata == null) { - logD("No metadata.") + logW("No metadata could be extracted from this track") volume = 1f return } @@ -102,7 +104,7 @@ class AudioReactor( // ReplayGain is configurable, so determine what to do based off of the mode. val useAlbumGain: (Gain) -> Boolean = when (settingsManager.replayGainMode) { ReplayGainMode.OFF -> { - logD("ReplayGain is off.") + logD("ReplayGain is off") volume = 1f return } @@ -132,10 +134,10 @@ class AudioReactor( val adjust = if (gain != null) { if (useAlbumGain(gain)) { - logD("Using album gain.") + logD("Using album gain") gain.album } else { - logD("Using track gain.") + logD("Using track gain") gain.track } } else { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt index 0173fc7b5..8cde108b2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt @@ -5,6 +5,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat +import org.oxycblt.auxio.util.logD /** * Some apps like to party like it's 2011 and just blindly query for the ACTION_MEDIA_BUTTON @@ -20,6 +21,7 @@ import androidx.core.content.ContextCompat class MediaButtonReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == Intent.ACTION_MEDIA_BUTTON) { + logD("Received external media button intent") intent.component = ComponentName(context, PlaybackService::class.java) ContextCompat.startForegroundService(context, intent) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 78cbb9a2f..d9842ffa9 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -180,7 +180,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac settingsManager.addCallback(this) - logD("Service created.") + logD("Service created") } override fun onDestroy() { @@ -207,7 +207,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac serviceJob.cancel() } - logD("Service destroyed.") + logD("Service destroyed") } // --- PLAYER EVENT LISTENER OVERRIDES --- @@ -260,22 +260,21 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac override fun onSongUpdate(song: Song?) { if (song != null) { + logD("Setting player to ${song.name}") player.setMediaItem(MediaItem.fromUri(song.uri)) player.prepare() - notification.setMetadata(song, ::startForegroundOrNotify) - return } // Clear if there's nothing to play. + logD("Nothing playing, stopping playback") player.stop() stopForegroundAndNotification() } override fun onParentUpdate(parent: MusicParent?) { notification.setParent(parent) - startForegroundOrNotify() } @@ -295,7 +294,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac override fun onLoopUpdate(loopMode: LoopMode) { if (!settingsManager.useAltNotifAction) { notification.setLoop(loopMode) - startForegroundOrNotify() } } @@ -303,7 +301,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac override fun onShuffleUpdate(isShuffling: Boolean) { if (settingsManager.useAltNotifAction) { notification.setShuffle(isShuffling) - startForegroundOrNotify() } } @@ -334,7 +331,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac override fun onShowCoverUpdate(showCovers: Boolean) { playbackManager.song?.let { song -> connector.onSongUpdate(song) - notification.setMetadata(song, ::startForegroundOrNotify) } } @@ -449,6 +445,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac /** * A [BroadcastReceiver] for receiving general playback events from the system. + * TODO: Don't fire when the service initially starts? */ private inner class PlaybackReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -501,7 +498,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac */ private fun resumeFromPlug() { if (playbackManager.song != null && settingsManager.doPlugMgt) { - logD("Device connected, resuming...") + logD("Device connected, resuming") playbackManager.setPlaying(true) } } @@ -511,7 +508,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac */ private fun pauseFromPlug() { if (playbackManager.song != null && settingsManager.doPlugMgt) { - logD("Device disconnected, pausing...") + logD("Device disconnected, pausing") playbackManager.setPlaying(false) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt index 336667cf3..0c211c683 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt @@ -29,6 +29,7 @@ import org.oxycblt.auxio.coil.loadBitmap import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.PlaybackStateManager +import org.oxycblt.auxio.util.logD /** * Nightmarish class that coordinates communication between [MediaSessionCompat], [Player], @@ -158,6 +159,8 @@ class PlaybackSessionConnector( // --- MISC --- private fun invalidateSessionState() { + logD("Updating media session state") + // Position updates arrive faster when you upload STATE_PAUSED for some insane reason. val state = PlaybackStateCompat.Builder() .setActions(ACTIONS) 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 8104c55a0..b7ea8a1ec 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt @@ -52,7 +52,6 @@ class SearchAdapter( is Album -> AlbumViewHolder.ITEM_TYPE is Song -> SongViewHolder.ITEM_TYPE is Header -> HeaderViewHolder.ITEM_TYPE - else -> -1 } } @@ -77,7 +76,7 @@ class SearchAdapter( HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context) - else -> error("Invalid ViewHolder item type.") + else -> error("Invalid ViewHolder item type") } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index b39647326..f83e3b566 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -114,7 +114,6 @@ class SearchFragment : Fragment() { if (!launchedKeyboard) { // Auto-open the keyboard when this view is shown requestFocus() - postDelayed(200) { imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) } @@ -162,7 +161,7 @@ class SearchFragment : Fragment() { imm.hide() } - logD("Fragment created.") + logD("Fragment created") return binding.root } 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 4f9604edc..6c84a9157 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Sort +import org.oxycblt.auxio.util.logD import java.text.Normalizer /** @@ -70,11 +71,14 @@ class SearchViewModel : ViewModel() { mLastQuery = query if (query.isEmpty() || musicStore == null) { + logD("No music/query, ignoring search") mSearchResults.value = listOf() return } - // Searching can be quite expensive, so hop on a co-routine + logD("Performing search for $query") + + // Searching can be quite expensive, so get on a co-routine viewModelScope.launch { val sort = Sort.ByName(true) val results = mutableListOf() @@ -127,6 +131,8 @@ class SearchViewModel : ViewModel() { else -> null } + logD("Updating filter mode to $mFilterMode") + settingsManager.searchFilterMode = mFilterMode search(mLastQuery) 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 64cb1bf26..f24110e70 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -74,7 +74,7 @@ class AboutFragment : Fragment() { ) } - logD("Dialog created.") + logD("Dialog created") return binding.root } @@ -83,6 +83,8 @@ class AboutFragment : Fragment() { * Go through the process of opening a [link] in a browser. */ private fun openLinkInBrowser(link: String) { + logD("Opening $link") + val browserIntent = Intent(Intent.ACTION_VIEW, link.toUri()).setFlags( Intent.FLAG_ACTIVITY_NEW_TASK ) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt index 7c678052e..8ee87c17e 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt @@ -22,8 +22,7 @@ import android.content.SharedPreferences import androidx.core.content.edit import org.oxycblt.auxio.accent.Accent -// A couple of utils for migrating from old settings values to the new -// formats used in 1.3.2 & 1.4.0 +// A couple of utils for migrating from old settings values to the new formats fun handleAccentCompat(prefs: SharedPreferences): Accent { if (prefs.contains(OldKeys.KEY_ACCENT2)) { 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 e20a184fb..525d5654d 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt @@ -31,7 +31,7 @@ import androidx.preference.children import androidx.recyclerview.widget.RecyclerView import coil.Coil import org.oxycblt.auxio.R -import org.oxycblt.auxio.accent.AccentDialog +import org.oxycblt.auxio.accent.AccentCustomizeDialog import org.oxycblt.auxio.excluded.ExcludedDialog import org.oxycblt.auxio.home.tabs.TabCustomizeDialog import org.oxycblt.auxio.playback.PlaybackViewModel @@ -68,7 +68,7 @@ class SettingsListFragment : PreferenceFragmentCompat() { } } - logD("Fragment created.") + logD("Fragment created") } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -119,7 +119,7 @@ class SettingsListFragment : PreferenceFragmentCompat() { SettingsManager.KEY_ACCENT -> { onPreferenceClickListener = Preference.OnPreferenceClickListener { - AccentDialog().show(childFragmentManager, AccentDialog.TAG) + AccentCustomizeDialog().show(childFragmentManager, AccentCustomizeDialog.TAG) true } @@ -182,7 +182,6 @@ class SettingsListFragment : PreferenceFragmentCompat() { AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> R.drawable.ic_auto AppCompatDelegate.MODE_NIGHT_NO -> R.drawable.ic_day AppCompatDelegate.MODE_NIGHT_YES -> R.drawable.ic_night - else -> R.drawable.ic_auto } } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt index c2e3c311b..8557cabc2 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt @@ -331,7 +331,7 @@ class SettingsManager private constructor(context: Context) : return instance } - error("SettingsManager must be initialized with init() before getting its instance.") + error("SettingsManager must be initialized with init() before getting its instance") } } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt index 60f6ff74c..8ecaf814a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt @@ -29,7 +29,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.res.ResourcesCompat import androidx.core.view.updatePadding import com.google.android.material.appbar.AppBarLayout -import org.oxycblt.auxio.util.logE +import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.systemBarInsetsCompat /** @@ -51,7 +51,6 @@ open class EdgeAppBarLayout @JvmOverloads constructor( if (child != null) { val coordinator = parent as CoordinatorLayout - (layoutParams as CoordinatorLayout.LayoutParams).behavior?.onNestedPreScroll( coordinator, this, coordinator, 0, 0, tConsumed, 0 ) @@ -66,15 +65,12 @@ open class EdgeAppBarLayout @JvmOverloads constructor( override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { super.onApplyWindowInsets(insets) - updatePadding(top = insets.systemBarInsetsCompat.top) - return insets } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - viewTreeObserver.removeOnPreDrawListener(onPreDraw) } @@ -94,9 +90,10 @@ open class EdgeAppBarLayout @JvmOverloads constructor( if (liftOnScrollTargetViewId != ResourcesCompat.ID_NULL) { scrollingChild = (parent as ViewGroup).findViewById(liftOnScrollTargetViewId) } else { - logE("liftOnScrollTargetViewId was not specified. ignoring scroll events.") + logW("liftOnScrollTargetViewId was not specified. ignoring scroll events") } } + return scrollingChild } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt b/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt index b460b7290..811f8d2e1 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt @@ -73,7 +73,7 @@ class MemberBinder( val lifecycle = fragment.viewLifecycleOwner.lifecycle check(lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { - "Fragment views are destroyed." + "Fragment views are destroyed" } // Otherwise create the binding and return that. diff --git a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt index 148b00110..77ca4c7b2 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt @@ -39,7 +39,6 @@ import androidx.annotation.PluralsRes import androidx.annotation.Px import androidx.annotation.StringRes import androidx.core.content.ContextCompat -import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.MainActivity import kotlin.reflect.KClass import kotlin.system.exitProcess @@ -190,16 +189,9 @@ fun Context.pxOfDp(@Dimension dp: Float): Int { } private fun Context.handleResourceFailure(e: Exception, what: String, default: T): T { - logE("$what load failed.") - - if (BuildConfig.DEBUG) { - // I'd rather be aware of a sudden crash when debugging. - throw e - } else { - // Not so much when the app is in production. - logE(e.stackTraceToString()) - return default - } + logE("$what load failed") + e.logTraceOrThrow() + return default } /** diff --git a/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt index 8895960a6..5d09b5096 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt @@ -34,7 +34,7 @@ fun SQLiteDatabase.queryAll(tableName: String, block: (Cursor) -> R) = */ fun assertBackgroundThread() { check(Looper.myLooper() != Looper.getMainLooper()) { - "This operation must be ran on a background thread." + "This operation must be ran on a background thread" } } diff --git a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt index fc5bdb105..500b65df9 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt @@ -41,6 +41,13 @@ fun Any.logD(msg: String) { } } +/** + * Shortcut method for logging [msg] as a warning to the console. Handles anonymous objects + */ +fun Any.logW(msg: String) { + Log.w(getName(), msg) +} + /** * Shortcut method for logging [msg] as an error to the console. Handles anonymous objects */ @@ -48,6 +55,18 @@ fun Any.logE(msg: String) { Log.e(getName(), msg) } +/** + * Logs an error in production while still throwing it in debug mode. This is useful for + * non-showstopper bugs that I would still prefer to be caught in debug mode. + */ +fun Throwable.logTraceOrThrow() { + if (BuildConfig.DEBUG) { + throw this + } else { + logE(stackTraceToString()) + } +} + /** * Get a non-nullable name, used so that logs will always show up by Auxio * @return The name of the object, otherwise "Anonymous Object" diff --git a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt index 5ca64eacc..7f1c9773e 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt @@ -69,6 +69,7 @@ fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height */ fun View.disableDropShadowCompat() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + logD("Disabling drop shadows") val transparent = context.getColorSafe(android.R.color.transparent) outlineAmbientShadowColor = transparent outlineSpotShadowColor = transparent diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt index c28a5b560..10bd5026c 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt @@ -23,6 +23,7 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.SettingsManager +import org.oxycblt.auxio.util.logD /** * A wrapper around each [WidgetProvider] that plugs into the main Auxio process and updates the @@ -53,6 +54,8 @@ class WidgetController(private val context: Context) : * Release this instance, removing the callbacks and resetting all widgets */ fun release() { + logD("Releasing instance") + widget.reset(context) playbackManager.removeCallback(this) settingsManager.removeCallback(this) diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt index dad9770de..96dbf54ed 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -40,6 +40,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.util.getDimenSizeSafe import org.oxycblt.auxio.util.isLandscape import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.logW import kotlin.math.min /** @@ -87,6 +88,10 @@ class WidgetProvider : AppWidgetProvider() { } } + /** + * Custom function for loading bitmaps to the widget in a way that works with the + * widget ImageView instances. + */ private fun loadWidgetBitmap(context: Context, song: Song, onDone: (Bitmap?) -> Unit) { val coverRequest = ImageRequest.Builder(context) .data(song.album) @@ -152,6 +157,8 @@ class WidgetProvider : AppWidgetProvider() { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + logD("Requesting new view from PlaybackService") + // We can't resize the widget until we can generate the views, so request an update // from PlaybackService. requestUpdate(context) @@ -234,7 +241,7 @@ class WidgetProvider : AppWidgetProvider() { continue } else { // Default to the smallest view if no layout fits - logD("No widget layout found") + logW("No good widget layout found") val minimum = requireNotNull( views.minByOrNull { it.key.width * it.key.height }?.value diff --git a/app/src/main/res/layout-sw640dp/view_playback_bar.xml b/app/src/main/res/layout-sw640dp/view_playback_bar.xml index d2238a9fd..19356f679 100644 --- a/app/src/main/res/layout-sw640dp/view_playback_bar.xml +++ b/app/src/main/res/layout-sw640dp/view_playback_bar.xml @@ -52,7 +52,7 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/spacing_small" android:ellipsize="end" - android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}" + app:songInfo="@{song}" app:layout_constraintBottom_toBottomOf="@+id/playback_cover" app:layout_constraintEnd_toEndOf="@+id/playback_song" app:layout_constraintStart_toEndOf="@+id/playback_cover" diff --git a/app/src/main/res/layout-w600dp/view_playback_bar.xml b/app/src/main/res/layout-w600dp/view_playback_bar.xml index 94a1484f3..63dbed1bc 100644 --- a/app/src/main/res/layout-w600dp/view_playback_bar.xml +++ b/app/src/main/res/layout-w600dp/view_playback_bar.xml @@ -50,7 +50,7 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/spacing_small" android:ellipsize="end" - android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}" + app:songInfo="@{song}" app:layout_constraintBottom_toBottomOf="@+id/playback_cover" app:layout_constraintEnd_toEndOf="@+id/playback_song" app:layout_constraintStart_toEndOf="@+id/playback_cover" diff --git a/app/src/main/res/layout/dialog_accent.xml b/app/src/main/res/layout/dialog_accent.xml index 31af1a5a7..68c6f1609 100644 --- a/app/src/main/res/layout/dialog_accent.xml +++ b/app/src/main/res/layout/dialog_accent.xml @@ -12,7 +12,7 @@ android:paddingTop="@dimen/spacing_medium" android:paddingEnd="@dimen/spacing_medium" android:paddingBottom="@dimen/spacing_small" - app:layoutManager="org.oxycblt.auxio.accent.AutoGridLayoutManager" + app:layoutManager="org.oxycblt.auxio.accent.AccentGridLayoutManager" app:layout_constraintBottom_toTopOf="@+id/accent_cancel" app:layout_constraintTop_toBottomOf="@+id/accent_header" tools:itemCount="18" diff --git a/app/src/main/res/layout/item_album.xml b/app/src/main/res/layout/item_album.xml index 898936cac..66eb8006f 100644 --- a/app/src/main/res/layout/item_album.xml +++ b/app/src/main/res/layout/item_album.xml @@ -41,7 +41,7 @@ style="@style/Widget.Auxio.TextView.Item.Secondary" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@{@string/fmt_two(album.resolvedArtistName, @plurals/fmt_song_count(album.songs.size, album.songs.size))}" + app:albumInfo="@{album}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/album_cover" diff --git a/app/src/main/res/layout/item_artist.xml b/app/src/main/res/layout/item_artist.xml index c4e4c138b..81ce94239 100644 --- a/app/src/main/res/layout/item_artist.xml +++ b/app/src/main/res/layout/item_artist.xml @@ -41,7 +41,7 @@ style="@style/Widget.Auxio.TextView.Item.Secondary" android:layout_width="0dp" android:layout_height="wrap_content" - app:artistCounts="@{artist}" + app:artistInfo="@{artist}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/artist_image" diff --git a/app/src/main/res/layout/item_genre.xml b/app/src/main/res/layout/item_genre.xml index 8fb90cb02..5d2109065 100644 --- a/app/src/main/res/layout/item_genre.xml +++ b/app/src/main/res/layout/item_genre.xml @@ -41,7 +41,7 @@ style="@style/Widget.Auxio.TextView.Item.Secondary" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@{@plurals/fmt_song_count(genre.songs.size(), genre.songs.size())}" + app:genreInfo="@{genre}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/genre_image" diff --git a/app/src/main/res/layout/item_genre_song.xml b/app/src/main/res/layout/item_genre_song.xml index 7d6278c9d..4070e6393 100644 --- a/app/src/main/res/layout/item_genre_song.xml +++ b/app/src/main/res/layout/item_genre_song.xml @@ -44,7 +44,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/spacing_medium" - android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}" + app:songInfo="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/song_duration" app:layout_constraintStart_toEndOf="@+id/album_cover" diff --git a/app/src/main/res/layout/item_queue_song.xml b/app/src/main/res/layout/item_queue_song.xml index cf9580e97..2f12c6da8 100644 --- a/app/src/main/res/layout/item_queue_song.xml +++ b/app/src/main/res/layout/item_queue_song.xml @@ -69,7 +69,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/spacing_medium" - android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}" + app:songInfo="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/song_drag_handle" app:layout_constraintStart_toEndOf="@+id/album_cover" diff --git a/app/src/main/res/layout/item_song.xml b/app/src/main/res/layout/item_song.xml index 1082b050f..3e4313525 100644 --- a/app/src/main/res/layout/item_song.xml +++ b/app/src/main/res/layout/item_song.xml @@ -42,7 +42,7 @@ style="@style/Widget.Auxio.TextView.Item.Secondary" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}" + app:songInfo="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/album_cover" diff --git a/app/src/main/res/layout/view_playback_bar.xml b/app/src/main/res/layout/view_playback_bar.xml index d9034843e..e05fb7eab 100644 --- a/app/src/main/res/layout/view_playback_bar.xml +++ b/app/src/main/res/layout/view_playback_bar.xml @@ -51,7 +51,7 @@ android:layout_marginStart="@dimen/spacing_small" android:layout_marginEnd="@dimen/spacing_small" android:ellipsize="end" - android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}" + app:songInfo="@{song}" app:layout_constraintBottom_toBottomOf="@+id/playback_cover" app:layout_constraintEnd_toStartOf="@+id/playback_play_pause" app:layout_constraintStart_toEndOf="@+id/playback_cover" From 35b75b5f8132a837f44d3894d08deafab5896789 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 22 Feb 2022 17:43:21 -0700 Subject: [PATCH 18/38] music: rename BaseModel to Item Rename BaseModel to Item to make the meaning of it clearer. --- CHANGELOG.md | 1 + .../oxycblt/auxio/detail/DetailViewModel.kt | 26 +++++++++---------- .../detail/recycler/AlbumDetailAdapter.kt | 4 +-- .../detail/recycler/ArtistDetailAdapter.kt | 6 ++--- .../detail/recycler/GenreDetailAdapter.kt | 4 +-- .../auxio/home/list/HomeListFragment.kt | 6 ++--- .../java/org/oxycblt/auxio/music/Models.kt | 26 +++++++++++-------- .../auxio/playback/queue/QueueAdapter.kt | 6 ++--- .../org/oxycblt/auxio/search/SearchAdapter.kt | 4 +-- .../oxycblt/auxio/search/SearchViewModel.kt | 8 +++--- .../java/org/oxycblt/auxio/ui/ActionMenu.kt | 10 +++---- .../java/org/oxycblt/auxio/ui/DiffCallback.kt | 6 ++--- .../java/org/oxycblt/auxio/ui/ViewHolders.kt | 8 +++--- 13 files changed, 60 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4625c7228..c2a609d7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ #### Dev/Meta - Enabled elevation drop shadows below Android P for consistency - Reworked dynamic color usage +- Reworked logging ## v2.2.1 #### What's Improved 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 fed77ca96..31a41dc57 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -26,9 +26,9 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.music.ActionHeader 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.Header +import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.ui.DisplayMode @@ -49,30 +49,30 @@ class DetailViewModel : ViewModel() { private val mCurGenre = MutableLiveData() val curGenre: LiveData get() = mCurGenre - private val mGenreData = MutableLiveData(listOf()) - val genreData: LiveData> = mGenreData + private val mGenreData = MutableLiveData(listOf()) + val genreData: LiveData> = mGenreData private val mCurArtist = MutableLiveData() val curArtist: LiveData get() = mCurArtist - private val mArtistData = MutableLiveData(listOf()) - val artistData: LiveData> = mArtistData + private val mArtistData = MutableLiveData(listOf()) + val artistData: LiveData> = mArtistData private val mCurAlbum = MutableLiveData() val curAlbum: LiveData get() = mCurAlbum - private val mAlbumData = MutableLiveData(listOf()) - val albumData: LiveData> get() = mAlbumData + private val mAlbumData = MutableLiveData(listOf()) + val albumData: LiveData> get() = mAlbumData data class MenuConfig(val anchor: View, val sortMode: Sort) private val mShowMenu = MutableLiveData(null) val showMenu: LiveData = mShowMenu - private val mNavToItem = MutableLiveData() + private val mNavToItem = MutableLiveData() /** Flag for unified navigation. Observe this to coordinate navigation to an item's UI. */ - val navToItem: LiveData get() = mNavToItem + val navToItem: LiveData get() = mNavToItem var isNavigating = false private set @@ -133,7 +133,7 @@ class DetailViewModel : ViewModel() { /** * Navigate to an item, whether a song/album/artist */ - fun navToItem(item: BaseModel) { + fun navToItem(item: Item) { mNavToItem.value = item } @@ -154,7 +154,7 @@ class DetailViewModel : ViewModel() { private fun refreshGenreData() { logD("Refreshing genre data") val genre = requireNotNull(curGenre.value) - val data = mutableListOf(genre) + val data = mutableListOf(genre) data.add( ActionHeader( @@ -177,7 +177,7 @@ class DetailViewModel : ViewModel() { private fun refreshArtistData() { logD("Refreshing artist data") val artist = requireNotNull(curArtist.value) - val data = mutableListOf(artist) + val data = mutableListOf(artist) data.add( Header( @@ -209,7 +209,7 @@ class DetailViewModel : ViewModel() { private fun refreshAlbumData() { logD("Refreshing album data") val album = requireNotNull(curAlbum.value) - val data = mutableListOf(album) + val data = mutableListOf(album) data.add( ActionHeader( 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 f09acf501..49eb15549 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 @@ -30,7 +30,7 @@ import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.Album -import org.oxycblt.auxio.music.BaseModel +import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.toDate import org.oxycblt.auxio.playback.PlaybackViewModel @@ -49,7 +49,7 @@ class AlbumDetailAdapter( private val detailModel: DetailViewModel, private val doOnClick: (data: Song) -> Unit, private val doOnLongClick: (view: View, data: Song) -> Unit -) : ListAdapter(DiffCallback()) { +) : ListAdapter(DiffCallback()) { private var currentSong: Song? = null private var currentHolder: Highlightable? = null 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 614522ca7..e80eb2ce2 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 @@ -30,8 +30,8 @@ import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist -import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Header +import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.bindArtistInfo import org.oxycblt.auxio.playback.PlaybackViewModel @@ -49,8 +49,8 @@ class ArtistDetailAdapter( private val playbackModel: PlaybackViewModel, private val doOnClick: (data: Album) -> Unit, private val doOnSongClick: (data: Song) -> Unit, - private val doOnLongClick: (view: View, data: BaseModel) -> Unit, -) : ListAdapter(DiffCallback()) { + private val doOnLongClick: (view: View, data: Item) -> Unit, +) : ListAdapter(DiffCallback()) { private var currentAlbum: Album? = null private var currentAlbumHolder: Highlightable? = null 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 804b33561..11b2affbf 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt @@ -27,8 +27,8 @@ import org.oxycblt.auxio.coil.bindGenreImage import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.databinding.ItemGenreSongBinding import org.oxycblt.auxio.music.ActionHeader -import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.bindGenreInfo import org.oxycblt.auxio.playback.PlaybackViewModel @@ -45,7 +45,7 @@ class GenreDetailAdapter( private val playbackModel: PlaybackViewModel, private val doOnClick: (data: Song) -> Unit, private val doOnLongClick: (view: View, data: Song) -> Unit -) : ListAdapter(DiffCallback()) { +) : ListAdapter(DiffCallback()) { private var currentSong: Song? = null private var currentHolder: Highlightable? = null diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt index 8b5f8ecc3..25a73343c 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt @@ -26,7 +26,7 @@ import androidx.lifecycle.LiveData import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.home.HomeViewModel -import org.oxycblt.auxio.music.BaseModel +import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.memberBinding import org.oxycblt.auxio.util.applySpans @@ -48,7 +48,7 @@ abstract class HomeListFragment : Fragment() { */ abstract val listPopupProvider: (Int) -> String - protected fun setupRecycler( + protected fun setupRecycler( @IdRes uniqueId: Int, homeAdapter: HomeAdapter, homeData: LiveData>, @@ -71,7 +71,7 @@ abstract class HomeListFragment : Fragment() { } } - abstract class HomeAdapter : RecyclerView.Adapter() { + abstract class HomeAdapter : RecyclerView.Adapter() { protected var data = listOf() @SuppressLint("NotifyDataSetChanged") diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index d4f7e296c..c42b7a6c7 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -28,33 +28,37 @@ import androidx.annotation.StringRes // --- MUSIC MODELS --- /** - * The base data object for all music. - * @property id A unique ID for this object. ***THIS IS NOT A MEDIASTORE ID!** + * The template for all items in Auxio. */ -sealed class BaseModel { +sealed class Item { + /** A unique ID for this item. ***THIS IS NOT A MEDIASTORE ID!** */ abstract val id: Long } /** - * A [BaseModel] variant that represents a music item. - * @property name The raw name of this track + * A [Item] variant that represents a music item. + * @property name */ -sealed class Music : BaseModel() { +sealed class Music : Item() { + /** The raw name of this item. */ abstract val name: String } /** * [Music] variant that denotes that this object is a parent of other data objects, such * as an [Album] or [Artist] - * @property resolvedName A name resolved from it's raw form to a form suitable to be shown in - * a ui. Ex. "unknown" would become Unknown Artist, (124) would become its proper genre name, etc. + * @property resolvedName */ sealed class MusicParent : Music() { + /** + * A name resolved from it's raw form to a form suitable to be shown in a ui. + * Ex. "unknown" would become Unknown Artist, (124) would become its proper genre name, etc. + */ abstract val resolvedName: String } /** - * The data object for a song. Inherits [BaseModel]. + * The data object for a song. Inherits [Item]. */ data class Song( override val name: String, @@ -243,7 +247,7 @@ data class Header( override val id: Long, /** The string resource used for the header. */ @StringRes val string: Int -) : BaseModel() +) : Item() /** * A data object used for an action header. Like [Header], but with a button. @@ -259,7 +263,7 @@ data class ActionHeader( @StringRes val desc: Int, /** A callback for when this item is clicked. */ val onClick: (View) -> Unit, -) : BaseModel() { +) : Item() { // All lambdas are not equal to each-other, so we override equals/hashCode and exclude them. override fun equals(other: Any?): Boolean { 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 6c6970662..cfc18f848 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 @@ -30,8 +30,8 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.databinding.ItemQueueSongBinding import org.oxycblt.auxio.music.ActionHeader -import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Header +import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.ui.ActionHeaderViewHolder import org.oxycblt.auxio.ui.BaseViewHolder @@ -50,7 +50,7 @@ import org.oxycblt.auxio.util.stateList class QueueAdapter( private val touchHelper: ItemTouchHelper ) : RecyclerView.Adapter() { - private var data = mutableListOf() + private var data = mutableListOf() private var listDiffer = AsyncListDiffer(this, DiffCallback()) override fun getItemCount(): Int = data.size @@ -89,7 +89,7 @@ class QueueAdapter( * Submit data using [AsyncListDiffer]. * **Only use this if you have no idea what changes occurred to the data** */ - fun submitList(newData: MutableList) { + fun submitList(newData: MutableList) { if (data != newData) { data = newData listDiffer.submitList(newData) 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 b7ea8a1ec..3a7059520 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt @@ -24,9 +24,9 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView 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.Header +import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.ui.AlbumViewHolder @@ -43,7 +43,7 @@ import org.oxycblt.auxio.ui.SongViewHolder class SearchAdapter( private val doOnClick: (data: Music) -> Unit, private val doOnLongClick: (view: View, data: Music) -> Unit -) : ListAdapter(DiffCallback()) { +) : ListAdapter(DiffCallback()) { override fun getItemViewType(position: Int): Int { return when (getItem(position)) { 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 6c84a9157..2f5445651 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -25,8 +25,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Header +import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicStore @@ -41,13 +41,13 @@ import java.text.Normalizer * @author OxygenCobalt */ class SearchViewModel : ViewModel() { - private val mSearchResults = MutableLiveData(listOf()) + private val mSearchResults = MutableLiveData(listOf()) private var mIsNavigating = false private var mFilterMode: DisplayMode? = null private var mLastQuery = "" /** Current search results from the last [search] call. */ - val searchResults: LiveData> get() = mSearchResults + val searchResults: LiveData> get() = mSearchResults val isNavigating: Boolean get() = mIsNavigating val filterMode: DisplayMode? get() = mFilterMode @@ -81,7 +81,7 @@ class SearchViewModel : ViewModel() { // Searching can be quite expensive, so get on a co-routine viewModelScope.launch { val sort = Sort.ByName(true) - val results = mutableListOf() + val results = mutableListOf() // Note: a filter mode of null means to not filter at all. diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt index d38095891..222870782 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt @@ -30,8 +30,8 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.detail.DetailViewModel 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.Item import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.util.showToast @@ -39,11 +39,11 @@ import org.oxycblt.auxio.util.showToast /** * Extension method for creating and showing a new [ActionMenu]. * @param anchor [View] This should be centered around - * @param data [BaseModel] this menu corresponds to + * @param data [Item] this menu corresponds to * @param flag (Optional, defaults to [ActionMenu.FLAG_NONE]) Any extra flags to accompany the data. * @see ActionMenu */ -fun Fragment.newMenu(anchor: View, data: BaseModel, flag: Int = ActionMenu.FLAG_NONE) { +fun Fragment.newMenu(anchor: View, data: Item, flag: Int = ActionMenu.FLAG_NONE) { ActionMenu(requireActivity() as AppCompatActivity, anchor, data, flag).show() } @@ -51,7 +51,7 @@ fun Fragment.newMenu(anchor: View, data: BaseModel, flag: Int = ActionMenu.FLAG_ * A wrapper around [PopupMenu] that automates the menu creation for nearly every datatype in Auxio. * @param activity [AppCompatActivity] required as both a context and ViewModelStore owner. * @param anchor [View] This should be centered around - * @param data [BaseModel] this menu corresponds to + * @param data [Item] this menu corresponds to * @param flag Any extra flags to accompany the data. See [FLAG_NONE], [FLAG_IN_ALBUM], [FLAG_IN_ARTIST], [FLAG_IN_GENRE] for more details. * @throws IllegalStateException When there is no menu for this specific datatype/flag * @author OxygenCobalt @@ -59,7 +59,7 @@ fun Fragment.newMenu(anchor: View, data: BaseModel, flag: Int = ActionMenu.FLAG_ class ActionMenu( activity: AppCompatActivity, anchor: View, - private val data: BaseModel, + private val data: Item, private val flag: Int ) : PopupMenu(activity, anchor) { private val context = activity.applicationContext diff --git a/app/src/main/java/org/oxycblt/auxio/ui/DiffCallback.kt b/app/src/main/java/org/oxycblt/auxio/ui/DiffCallback.kt index 768a3a63d..5ed786da4 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/DiffCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/DiffCallback.kt @@ -19,14 +19,14 @@ package org.oxycblt.auxio.ui import androidx.recyclerview.widget.DiffUtil -import org.oxycblt.auxio.music.BaseModel +import org.oxycblt.auxio.music.Item /** - * A re-usable diff callback for all [BaseModel] implementations. + * A re-usable diff callback for all [Item] implementations. * **Use this instead of creating a DiffCallback for each adapter.** * @author OxygenCobalt */ -class DiffCallback : DiffUtil.ItemCallback() { +class DiffCallback : DiffUtil.ItemCallback() { override fun areContentsTheSame(oldItem: T, newItem: T): Boolean { return oldItem.hashCode() == newItem.hashCode() } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt index 2b700a774..975c23ca1 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt @@ -32,21 +32,21 @@ import org.oxycblt.auxio.databinding.ItemSongBinding import org.oxycblt.auxio.music.ActionHeader 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.Header +import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.util.inflater /** * A [RecyclerView.ViewHolder] that streamlines a lot of the common things across all viewholders. - * @param T The datatype, inheriting [BaseModel] for this ViewHolder. + * @param T The datatype, inheriting [Item] for this ViewHolder. * @param binding Basic [ViewDataBinding] required to set up click listeners & sizing. * @param doOnClick (Optional) Function that calls on a click. * @param doOnLongClick (Optional) Functions that calls on a long-click. * @author OxygenCobalt */ -abstract class BaseViewHolder( +abstract class BaseViewHolder( private val binding: ViewDataBinding, private val doOnClick: ((data: T) -> Unit)? = null, private val doOnLongClick: ((view: View, data: T) -> Unit)? = null @@ -59,7 +59,7 @@ abstract class BaseViewHolder( } /** - * Bind the viewholder with whatever [BaseModel] instance that has been specified. + * Bind the viewholder with whatever [Item] instance that has been specified. * Will call [onBind] on the inheriting ViewHolder. * @param data Data that the viewholder should be bound with */ From 22258a3e6bee2b299c2947b96a836d818ee0ddda Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 22 Feb 2022 17:44:56 -0700 Subject: [PATCH 19/38] style: add disabled state to switches Add a disabled state to the M3 switches. --- CHANGELOG.md | 1 + app/src/main/res/color/sel_m3_switch_thumb.xml | 1 + app/src/main/res/color/sel_m3_switch_track.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a609d7f..4a3c820ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ #### Dev/Meta - Enabled elevation drop shadows below Android P for consistency +- Switches now have a disabled state - Reworked dynamic color usage - Reworked logging diff --git a/app/src/main/res/color/sel_m3_switch_thumb.xml b/app/src/main/res/color/sel_m3_switch_thumb.xml index 61c22f9ad..6dc73cbb1 100644 --- a/app/src/main/res/color/sel_m3_switch_thumb.xml +++ b/app/src/main/res/color/sel_m3_switch_thumb.xml @@ -1,5 +1,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/color/sel_m3_switch_track.xml b/app/src/main/res/color/sel_m3_switch_track.xml index 5d2e6df2f..c3abdac07 100644 --- a/app/src/main/res/color/sel_m3_switch_track.xml +++ b/app/src/main/res/color/sel_m3_switch_track.xml @@ -1,5 +1,6 @@ + \ No newline at end of file From 87035805d284a584ed9601d12220d7b0214a8f9c Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Wed, 23 Feb 2022 15:22:41 -0700 Subject: [PATCH 20/38] ui: general cleanup Clean up a bunch of UI issues that have accumulated. --- CHANGELOG.md | 2 +- .../java/org/oxycblt/auxio/accent/Accent.kt | 2 +- .../auxio/detail/DetailAppBarLayout.kt | 6 +++--- .../home/FloatingActionButtonContainer.kt | 3 ++- .../home/fastscroll/FastScrollRecyclerView.kt | 3 ++- .../java/org/oxycblt/auxio/music/Models.kt | 4 ++-- .../oxycblt/auxio/playback/PlaybackBarView.kt | 2 +- .../oxycblt/auxio/playback/PlaybackButton.kt | 5 +++-- .../oxycblt/auxio/playback/PlaybackSeekBar.kt | 2 +- .../auxio/settings/pref/IntListPreference.kt | 19 ++++++++++--------- .../org/oxycblt/auxio/ui/EdgeAppBarLayout.kt | 4 ++-- .../oxycblt/auxio/ui/EdgeCoordinatorLayout.kt | 3 ++- .../org/oxycblt/auxio/ui/EdgeRecyclerView.kt | 3 ++- .../drawable/ui_large_unbounded_ripple.xml | 2 +- .../main/res/drawable/ui_unbounded_ripple.xml | 2 +- .../res/layout-land/fragment_playback.xml | 4 ---- .../layout-sw600dp-land/fragment_playback.xml | 4 ---- .../res/layout-sw600dp/fragment_playback.xml | 4 ---- .../res/layout-sw640dp/view_playback_bar.xml | 3 --- .../res/layout-w600dp/view_playback_bar.xml | 7 ++----- app/src/main/res/layout/fragment_about.xml | 8 +------- app/src/main/res/layout/item_excluded_dir.xml | 4 +++- app/src/main/res/layout/view_playback_bar.xml | 1 - app/src/main/res/layout/view_seek_bar.xml | 1 + app/src/main/res/values-zh-rCN/strings.xml | 2 -- app/src/main/res/values/dimens.xml | 4 +--- .../res/values/{settings.xml => integers.xml} | 2 ++ app/src/main/res/values/styles_ui.xml | 4 ---- .../metadata/android/en-US/changelogs/1.txt | 2 +- info/ADDITIONS.md | 4 ++-- info/ARCHITECTURE.md | 6 +++--- 31 files changed, 50 insertions(+), 72 deletions(-) rename app/src/main/res/values/{settings.xml => integers.xml} (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a3c820ea..2a43bb930 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -204,7 +204,7 @@ to when using gesture navigation - Fixed issue where the scroll thumb would briefly display on the Songs UI - Fixed issue where fast scrolling could be triggered outside the bounds of the indicators - Fixed issue where the wrong playing item would be highlighted if the names were identical -- Fixed a crash when the thumb was moved above the fast scroller [Backported to 1.3.3, included in this release officially] +- Fixed a crash when the thumb was moved above the fast scroller [Back-ported to 1.3.3, included in this release officially] #### Dev/Meta - Migrated fully to material design diff --git a/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt index 97205cb44..e496f8193 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt @@ -101,7 +101,7 @@ private val ACCENT_PRIMARY_COLORS = arrayOf( /** * The data object for an accent. In the UI this is known as a "Color Scheme." * This can be nominally used to gleam some attributes about a given color scheme, but this - * is not recommended. Attributes are usually the better option in nearly all cases. + * is not recommended. Attributes are the better option in nearly all cases. * * @property name The name of this accent * @property theme The theme resource for this accent diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt index 6a073aff6..954215565 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt @@ -5,7 +5,7 @@ import android.content.Context import android.util.AttributeSet import android.view.View import android.view.ViewGroup -import androidx.annotation.StyleRes +import androidx.annotation.AttrRes import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout @@ -28,7 +28,7 @@ import java.lang.Exception class DetailAppBarLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - @StyleRes defStyleAttr: Int = -1 + @AttrRes defStyleAttr: Int = 0 ) : EdgeAppBarLayout(context, attrs, defStyleAttr) { private var mTitleView: AppCompatTextView? = null private var mRecycler: RecyclerView? = null @@ -109,7 +109,7 @@ class DetailAppBarLayout @JvmOverloads constructor( titleView?.alpha = it.animatedValue as Float } - duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong() + duration = resources.getInteger(R.integer.detail_app_bar_title_anim_duration).toLong() start() } diff --git a/app/src/main/java/org/oxycblt/auxio/home/FloatingActionButtonContainer.kt b/app/src/main/java/org/oxycblt/auxio/home/FloatingActionButtonContainer.kt index 863306dc9..9f38464dd 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/FloatingActionButtonContainer.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/FloatingActionButtonContainer.kt @@ -22,6 +22,7 @@ import android.content.Context import android.util.AttributeSet import android.view.WindowInsets import android.widget.FrameLayout +import androidx.annotation.AttrRes import androidx.core.view.updatePadding import org.oxycblt.auxio.util.systemBarInsetsCompat @@ -32,7 +33,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat class FloatingActionButtonContainer @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = -1 + @AttrRes defStyleAttr: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr) { init { clipToPadding = false diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt index 77a1ec5e2..bd08de937 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt @@ -32,6 +32,7 @@ import android.view.ViewGroup import android.view.WindowInsets import android.widget.FrameLayout import android.widget.TextView +import androidx.annotation.AttrRes import androidx.appcompat.widget.AppCompatTextView import androidx.core.math.MathUtils import androidx.core.view.isInvisible @@ -77,7 +78,7 @@ import kotlin.math.abs class FastScrollRecyclerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = -1 + @AttrRes defStyleAttr: Int = 0 ) : RecyclerView(context, attrs, defStyleAttr) { /** Callback to provide a string to be shown on the popup when an item is passed */ var popupProvider: ((Int) -> String)? = null diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index c42b7a6c7..1e3f80386 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -28,7 +28,7 @@ import androidx.annotation.StringRes // --- MUSIC MODELS --- /** - * The template for all items in Auxio. + * The base for all items in Auxio. */ sealed class Item { /** A unique ID for this item. ***THIS IS NOT A MEDIASTORE ID!** */ @@ -36,7 +36,7 @@ sealed class Item { } /** - * A [Item] variant that represents a music item. + * [Item] variant that represents a music item. * @property name */ sealed class Music : Item() { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt index 695170e97..caf863c05 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt @@ -41,7 +41,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat class PlaybackBarView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = -1 + defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { private val binding = ViewPlaybackBarBinding.inflate(context.inflater, this, true) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt index 523f2a2b6..b05777b81 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt @@ -5,6 +5,7 @@ import android.graphics.Canvas import android.graphics.Matrix import android.graphics.RectF import android.util.AttributeSet +import androidx.annotation.AttrRes import androidx.appcompat.widget.AppCompatImageButton import org.oxycblt.auxio.R import org.oxycblt.auxio.util.getDimenSizeSafe @@ -25,7 +26,7 @@ import org.oxycblt.auxio.util.getDrawableSafe class PlaybackButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = -1 + @AttrRes defStyleAttr: Int = 0 ) : AppCompatImageButton(context, attrs, defStyleAttr) { private val iconSize = context.getDimenSizeSafe(R.dimen.size_playback_icon) private val centerMatrix = Matrix() @@ -33,7 +34,7 @@ class PlaybackButton @JvmOverloads constructor( private val matrixDst = RectF() private val indicatorDrawable = context.getDrawableSafe(R.drawable.ui_indicator) - var hasIndicator = false + private var hasIndicator = false set(value) { field = value invalidate() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt index 274995005..4fae1f8e0 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt @@ -42,7 +42,7 @@ import org.oxycblt.auxio.util.stateList class PlaybackSeekBar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleRes: Int = -1 + defStyleRes: Int = 0 ) : ConstraintLayout(context, attrs, defStyleRes), Slider.OnChangeListener, Slider.OnSliderTouchListener { private val binding = ViewSeekBarBinding.inflate(context.inflater, this, true) private val isSeeking: Boolean get() = binding.playbackDurationCurrent.isActivated diff --git a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt b/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt index 6b163b8c2..78bdc08b4 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt @@ -18,25 +18,28 @@ package org.oxycblt.auxio.settings.pref -import android.annotation.SuppressLint import android.content.Context import android.content.res.TypedArray import android.util.AttributeSet import androidx.preference.DialogPreference +import androidx.preference.Preference import org.oxycblt.auxio.R -import androidx.preference.R as prefR class IntListPreference @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = prefR.attr.dialogPreferenceStyle, + defStyleAttr: Int = androidx.preference.R.attr.dialogPreferenceStyle, defStyleRes: Int = 0 ) : DialogPreference(context, attrs, defStyleAttr, defStyleRes) { + // Reflect into Preference to get the (normally inaccessible) default value. + private val defValueField = Preference::class.java.getDeclaredField("mDefaultValue").apply { + isAccessible = true + } + val entries: Array val values: IntArray - private var currentValue: Int? = null - private val defValue: Int + private val defValue: Int get() = defValueField.get(this) as Int init { val prefAttrs = context.obtainStyledAttributes( @@ -49,8 +52,6 @@ class IntListPreference @JvmOverloads constructor( prefAttrs.getResourceId(R.styleable.IntListPreference_entryValues, -1) ) - defValue = prefAttrs.getInt(prefR.styleable.Preference_defaultValue, Int.MIN_VALUE) - prefAttrs.recycle() summaryProvider = IntListSummaryProvider() @@ -96,7 +97,6 @@ class IntListPreference @JvmOverloads constructor( } } - @SuppressLint("PrivateResource") private inner class IntListSummaryProvider : SummaryProvider { override fun provideSummary(preference: IntListPreference): CharSequence { val index = getValueIndex() @@ -105,7 +105,8 @@ class IntListPreference @JvmOverloads constructor( return entries[index] } - return context.getString(prefR.string.not_set) + // Usually an invalid state, don't bother translating + return "" } } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt index 8ecaf814a..f30302ca0 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt @@ -24,7 +24,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import android.view.WindowInsets -import androidx.annotation.StyleRes +import androidx.annotation.AttrRes import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.res.ResourcesCompat import androidx.core.view.updatePadding @@ -41,7 +41,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat open class EdgeAppBarLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - @StyleRes defStyleAttr: Int = -1 + @AttrRes defStyleAttr: Int = 0 ) : AppBarLayout(context, attrs, defStyleAttr) { private var scrollingChild: View? = null private val tConsumed = IntArray(2) diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeCoordinatorLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeCoordinatorLayout.kt index 7e41af3fe..84341f7be 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeCoordinatorLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeCoordinatorLayout.kt @@ -21,6 +21,7 @@ package org.oxycblt.auxio.ui import android.content.Context import android.util.AttributeSet import android.view.WindowInsets +import androidx.annotation.AttrRes import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.children @@ -33,7 +34,7 @@ import androidx.core.view.children class EdgeCoordinatorLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = -1 + @AttrRes defStyleAttr: Int = 0 ) : CoordinatorLayout(context, attrs, defStyleAttr) { override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { for (child in children) { diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeRecyclerView.kt index 8f8fc4b1d..3a273a55b 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeRecyclerView.kt @@ -21,6 +21,7 @@ package org.oxycblt.auxio.ui import android.content.Context import android.util.AttributeSet import android.view.WindowInsets +import androidx.annotation.AttrRes import androidx.core.view.updatePadding import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.util.systemBarInsetsCompat @@ -31,7 +32,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat class EdgeRecyclerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = -1 + @AttrRes defStyleAttr: Int = 0 ) : RecyclerView(context, attrs, defStyleAttr) { override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { updatePadding(bottom = insets.systemBarInsetsCompat.bottom) diff --git a/app/src/main/res/drawable/ui_large_unbounded_ripple.xml b/app/src/main/res/drawable/ui_large_unbounded_ripple.xml index 3c999556f..8daf5be74 100644 --- a/app/src/main/res/drawable/ui_large_unbounded_ripple.xml +++ b/app/src/main/res/drawable/ui_large_unbounded_ripple.xml @@ -1,4 +1,4 @@ + android:radius="24dp" /> diff --git a/app/src/main/res/drawable/ui_unbounded_ripple.xml b/app/src/main/res/drawable/ui_unbounded_ripple.xml index 3118661f4..0b98cc5db 100644 --- a/app/src/main/res/drawable/ui_unbounded_ripple.xml +++ b/app/src/main/res/drawable/ui_unbounded_ripple.xml @@ -1,4 +1,4 @@ + android:radius="20dp" /> diff --git a/app/src/main/res/layout-land/fragment_playback.xml b/app/src/main/res/layout-land/fragment_playback.xml index 6a5d9cee3..eee455511 100644 --- a/app/src/main/res/layout-land/fragment_playback.xml +++ b/app/src/main/res/layout-land/fragment_playback.xml @@ -122,7 +122,6 @@ - - + android:layout_margin="@dimen/spacing_medium"> 已加载 %d 首曲目 - %d 首歌曲 "%d 首歌曲" - %d 张专辑 "%d 张专辑" diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 8b2794cad..f29dcde96 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,6 +1,7 @@ + 4dp 8dp 16dp 24dp @@ -16,9 +17,6 @@ 192dp 256dp - 20dp - 24dp - 32dp 32dp diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/integers.xml similarity index 96% rename from app/src/main/res/values/settings.xml rename to app/src/main/res/values/integers.xml index 691e1e77a..ef9360ddf 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/integers.xml @@ -1,5 +1,7 @@ + 150 + @string/set_theme_auto diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index 9f41449fb..95c53941b 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -151,10 +151,6 @@ - - diff --git a/fastlane/metadata/android/en-US/changelogs/1.txt b/fastlane/metadata/android/en-US/changelogs/1.txt index a443dae23..79589920f 100644 --- a/fastlane/metadata/android/en-US/changelogs/1.txt +++ b/fastlane/metadata/android/en-US/changelogs/1.txt @@ -1,5 +1,5 @@ - Redesigned the detail UIs -- Navigation is much more fluid and straightfoward +- Navigation is much more fluid and straightforward - Search has been moved to a dedicated tab - Added search filtering - Fixed issue where audio focus would resume playback when it shouldn't diff --git a/info/ADDITIONS.md b/info/ADDITIONS.md index 3681bdbcf..68b127a22 100644 --- a/info/ADDITIONS.md +++ b/info/ADDITIONS.md @@ -7,7 +7,7 @@ These will likely be accepted as long as they do not cause too much harm to the ## New Customizations/Options While I do like adding new behavior/UI customizations, these will be looked at more closely as certain additions can cause harm to the apps UI/UX while not providing alot of benefit. These tend to be accepted however. -## Feature Addtions and UI Changes +## Feature Additions and UI Changes These arent as likely to be accepted. As I said, I do not want Auxio to become overly bloated with features that are rarely used, therefore I only tend to accept features that: - Benefit **my own** usage @@ -22,6 +22,6 @@ Feel free to fork Auxio to add your own feature set however. - Recently added list [#18] (Out of scope) - Lyrics [#19] (Out of scope) - Tag editing [#33] (Out of scope) -- Gapless Playback [#35] (Technical issues) +- Gapless Playback [#35] (Technical issues, may change in the future) - Reduce leading instrument [#45] (Technical issues, Out of scope) - Opening music through a provider [#30] (Out of scope) diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md index 5ef8a9bb6..16248789f 100644 --- a/info/ARCHITECTURE.md +++ b/info/ARCHITECTURE.md @@ -52,13 +52,13 @@ is separated into three phases: - Set up the UI - Set up ViewModel instances and LiveData observers -`findViewById` is to **only** be used when interfacing with non-Auxio views. Otherwise, viewbinding should be -used in all cases. If one needs to keep track of a viewbinding outside of `onCreateView`, then one can declare +`findViewById` is to **only** be used when interfacing with non-Auxio views. Otherwise, view-binding should be +used in all cases. If one needs to keep track of a view-binding outside of `onCreateView`, then one can declare a binding `by memberBinding(BindingClass::inflate)` in order to have a binding that properly disposes itself on lifecycle events. At times it may be more appropriate to use a `View` instead of a full blown fragment. This is okay as long as -viewbinding is still used. +view-binding is still used. When creating a ViewHolder for a `RecyclerView`, one should use `BaseViewHolder` to standardize the binding process and automate some code shared across all ViewHolders. The only exceptions to this case are for ViewHolders that From 280b582efae96a3f58c28f7ecbbcab90972d6366 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Wed, 23 Feb 2022 16:13:59 -0700 Subject: [PATCH 21/38] home: rework fab adaptiveness Rework how the fab's adaptive functionality works. Handle fab adaptiveness by using a style instead of a fully custom view. This helps eliminate a usage of private resources. --- .../home/AdaptiveFloatingActionButton.kt | 40 ------------------- ...ButtonContainer.kt => EdgeFabContainer.kt} | 3 +- .../org/oxycblt/auxio/home/HomeFragment.kt | 14 +++---- app/src/main/res/layout/fragment_home.xml | 11 +++-- .../main/res/layout/view_shuffle_button.xml | 19 +++++++++ app/src/main/res/values-sw640dp/styles_ui.xml | 6 +++ app/src/main/res/values/styles_ui.xml | 7 +++- build.gradle | 2 +- 8 files changed, 44 insertions(+), 58 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt rename app/src/main/java/org/oxycblt/auxio/home/{FloatingActionButtonContainer.kt => EdgeFabContainer.kt} (96%) create mode 100644 app/src/main/res/layout/view_shuffle_button.xml create mode 100644 app/src/main/res/values-sw640dp/styles_ui.xml diff --git a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt deleted file mode 100644 index f6b405332..000000000 --- a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.oxycblt.auxio.home - -import android.content.Context -import android.util.AttributeSet -import com.google.android.material.floatingactionbutton.FloatingActionButton -import org.oxycblt.auxio.util.getDimenSizeSafe -import org.oxycblt.auxio.util.logD -import com.google.android.material.R as MaterialR - -/** - * A FloatingActionButton that automatically switches to a normal or large FAB depending on the - * screen size. - */ -@Suppress("PrivateResource") -class AdaptiveFloatingActionButton @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = MaterialR.style.Widget_Material3_FloatingActionButton_Primary -) : FloatingActionButton(context, attrs, defStyleAttr) { - - init { - size = SIZE_NORMAL - - // Use a large FAB on large screens, as it makes it easier to touch. - if (resources.configuration.smallestScreenWidthDp >= 640) { - logD("Using large FAB configuration") - - val largeFabSize = context.getDimenSizeSafe( - MaterialR.dimen.m3_large_fab_size - ) - - val largeImageSize = context.getDimenSizeSafe( - MaterialR.dimen.m3_large_fab_max_image_size - ) - - customSize = largeFabSize - setMaxImageSize(largeImageSize) - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/home/FloatingActionButtonContainer.kt b/app/src/main/java/org/oxycblt/auxio/home/EdgeFabContainer.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/home/FloatingActionButtonContainer.kt rename to app/src/main/java/org/oxycblt/auxio/home/EdgeFabContainer.kt index 9f38464dd..8828c62f6 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/FloatingActionButtonContainer.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/EdgeFabContainer.kt @@ -30,7 +30,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat * A container for a FloatingActionButton that enables edge-to-edge support. * @author OxygenCobalt */ -class FloatingActionButtonContainer @JvmOverloads constructor( +class EdgeFabContainer @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0 @@ -45,7 +45,6 @@ class FloatingActionButtonContainer @JvmOverloads constructor( override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets { updatePadding(bottom = insets.systemBarInsetsCompat.bottom) - return insets } } 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 b5b447ad4..f5fe3f216 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -159,11 +159,9 @@ class HomeFragment : Fragment() { ).attach() } - binding.homeFab.setOnClickListener { - playbackModel.shuffleAll() - } + binding.homeShuffleFab.setup(playbackModel) - // --- VIEWMODEL SETUP --- +// --- VIEWMODEL SETUP --- // There is no way a fast scrolling event can continue across a re-create. Reset it. homeModel.updateFastScrolling(false) @@ -171,12 +169,12 @@ class HomeFragment : Fragment() { musicModel.loaderResponse.observe(viewLifecycleOwner) { response -> // Handle the loader response. when (response) { - is MusicStore.Response.Ok -> binding.homeFab.show() + is MusicStore.Response.Ok -> binding.homeShuffleFab.show() // While loading or during an error, make sure we keep the shuffle fab hidden so // that any kind of playback is impossible. PlaybackStateManager also relies on this // invariant, so please don't change it. - else -> binding.homeFab.hide() + else -> binding.homeShuffleFab.hide() } } @@ -188,9 +186,9 @@ class HomeFragment : Fragment() { } if (scrolling) { - binding.homeFab.hide() + binding.homeShuffleFab.hide() } else { - binding.homeFab.show() + binding.homeShuffleFab.show() } } diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 4bb06bb87..2bab4386f 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -40,24 +40,23 @@ app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" tools:layout="@layout/fragment_home_list" /> - - + android:src="@drawable/ic_shuffle" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/view_shuffle_button.xml b/app/src/main/res/layout/view_shuffle_button.xml new file mode 100644 index 000000000..2065d961c --- /dev/null +++ b/app/src/main/res/layout/view_shuffle_button.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/values-sw640dp/styles_ui.xml b/app/src/main/res/values-sw640dp/styles_ui.xml new file mode 100644 index 000000000..f72e10d34 --- /dev/null +++ b/app/src/main/res/values-sw640dp/styles_ui.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index 95c53941b..ae84084c5 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -167,9 +167,14 @@ compensate. 2. For some reason elevation behaves strangely in the playback panel, so we disable it. --> - @dimen/size_playback_icon + normal @dimen/size_btn_large + @dimen/size_playback_icon 0dp 0dp + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7436375ca..773a522aa 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.1.1' + classpath 'com.android.tools.build:gradle:7.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" From 61dbfe31857a7d83688e36e1f3d27af4792e966b Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Wed, 23 Feb 2022 17:36:17 -0700 Subject: [PATCH 22/38] coil: make rounded images more nuanced Create a custom view for rounded images, making them more nuanced in the process. The previous method for applying rounded images in-app was generally clunky and fragile. Introduce a new custom view that actually takes a cornerRadius attribute from the ImageView itself that then applies it whenever the user enables the setting. This also allows rounded images to be more nuanced, as typical 8dp elevation can be used for small views and a more fitting 16dp radius can be used for large views. --- CHANGELOG.md | 1 + .../java/org/oxycblt/auxio/MainFragment.kt | 4 -- .../java/org/oxycblt/auxio/coil/CoilUtils.kt | 16 -------- .../oxycblt/auxio/coil/RoundableImageView.kt | 41 +++++++++++++++++++ .../org/oxycblt/auxio/home/HomeFragment.kt | 12 +++--- .../main/res/drawable/ui_rounded_cutout.xml | 5 --- .../main/res/layout-h600dp/item_detail.xml | 2 +- .../res/layout-land/fragment_playback.xml | 2 +- app/src/main/res/layout-land/item_detail.xml | 2 +- .../layout-sw600dp-land/fragment_playback.xml | 2 +- .../res/layout-sw600dp/fragment_playback.xml | 2 +- .../main/res/layout-sw600dp/item_detail.xml | 2 +- .../res/layout-sw640dp/view_playback_bar.xml | 2 +- .../main/res/layout-sw840dp/item_detail.xml | 2 +- .../layout-w600dp-land/fragment_playback.xml | 2 +- .../res/layout-w600dp/view_playback_bar.xml | 2 +- app/src/main/res/layout/fragment_playback.xml | 2 +- app/src/main/res/layout/item_album.xml | 2 +- app/src/main/res/layout/item_album_song.xml | 1 - app/src/main/res/layout/item_artist.xml | 2 +- app/src/main/res/layout/item_artist_album.xml | 2 +- app/src/main/res/layout/item_artist_song.xml | 2 +- app/src/main/res/layout/item_detail.xml | 2 +- app/src/main/res/layout/item_genre.xml | 2 +- app/src/main/res/layout/item_genre_song.xml | 2 +- app/src/main/res/layout/item_queue_song.xml | 2 +- app/src/main/res/layout/item_song.xml | 2 +- app/src/main/res/layout/view_playback_bar.xml | 2 +- app/src/main/res/values/attrs.xml | 4 ++ app/src/main/res/values/dimens.xml | 4 +- app/src/main/res/values/styles_ui.xml | 6 +++ 31 files changed, 83 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/coil/RoundableImageView.kt delete mode 100644 app/src/main/res/drawable/ui_rounded_cutout.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a43bb930..7e5b20bdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## dev [v2.2.2 or 2.3.0] #### What's Improved +- Rounded images are more nuanced - Shuffle and Repeat mode buttons now have more contrast when they are turned on #### What's Changed diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index a19d45c0a..b61c315c8 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -79,10 +79,6 @@ class MainFragment : Fragment() { // but for some insane reason google decided to cripple the window APIs one could use // to limit it's size. So, we just have our own special layout that is shown whenever // the screen is too small because of course we have to. - // Another fun fact: smallestScreenWidthDp is completely bugged and uses the total - // screen size, even when the window is smaller. This basically borks split screen - // even more than it already does. Fun! - if (requireActivity().isInMultiWindowMode) { val config = resources.configuration diff --git a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt index 85464857d..c65aff867 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt @@ -35,7 +35,6 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.settings.SettingsManager // --- BINDING ADAPTERS --- @@ -66,21 +65,6 @@ fun ImageView.bindGenreImage(genre: Genre?) = load(genre, R.drawable.ic_genre) fun ImageView.load(music: T?, @DrawableRes error: Int) { dispose() - // We don't round album covers by default as it desecrates album artwork, but we do provide - // an option if one wants it. - // As for why we use clipToOutline instead of coils RoundedCornersTransformation, the radii - // of an image's corners is dependent on the actual dimensions of the image, which would force - // us to resize all images to a fixed size. clipToOutline is pretty much always cheaper as long - // as we have a perfectly-square image. - val settingsManager = SettingsManager.getInstance() - if (settingsManager.roundCovers && background == null) { - setBackgroundResource(R.drawable.ui_rounded_cutout) - clipToOutline = true - } else if (!settingsManager.roundCovers && background != null) { - background = null - clipToOutline = false - } - load(music) { error(error) transformations(SquareFrameTransform()) diff --git a/app/src/main/java/org/oxycblt/auxio/coil/RoundableImageView.kt b/app/src/main/java/org/oxycblt/auxio/coil/RoundableImageView.kt new file mode 100644 index 000000000..61ff16ac3 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/coil/RoundableImageView.kt @@ -0,0 +1,41 @@ +package org.oxycblt.auxio.coil + +import android.content.Context +import android.util.AttributeSet +import androidx.annotation.AttrRes +import androidx.appcompat.widget.AppCompatImageView +import com.google.android.material.shape.MaterialShapeDrawable +import org.oxycblt.auxio.R +import org.oxycblt.auxio.settings.SettingsManager +import org.oxycblt.auxio.util.getColorSafe +import org.oxycblt.auxio.util.stateList + +class RoundableImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + @AttrRes defStyleAttr: Int = 0 +) : AppCompatImageView(context, attrs, defStyleAttr) { + init { + val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.RoundableImageView) + val cornerRadius = styledAttrs.getDimension(R.styleable.RoundableImageView_cornerRadius, 0.0f) + styledAttrs.recycle() + + background = MaterialShapeDrawable().apply { + setCornerSize(cornerRadius) + fillColor = context.getColorSafe(android.R.color.transparent).stateList + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + // We don't round album covers by default as it desecrates album artwork, but we do + // provide an option if one wants it. + // As for why we use clipToOutline instead of coils RoundedCornersTransformation, the radii + // of an image's corners is dependent on the actual dimensions of the image, which would + // force us to resize all images to a fixed size. clipToOutline is pretty much always + // cheaper as long as we have a perfectly-square image. + val settingsManager = SettingsManager.getInstance() + clipToOutline = settingsManager.roundCovers + } +} 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 f5fe3f216..c89ccb972 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -159,7 +159,9 @@ class HomeFragment : Fragment() { ).attach() } - binding.homeShuffleFab.setup(playbackModel) + binding.homeFab.setOnClickListener { + playbackModel.shuffleAll() + } // --- VIEWMODEL SETUP --- @@ -169,12 +171,12 @@ class HomeFragment : Fragment() { musicModel.loaderResponse.observe(viewLifecycleOwner) { response -> // Handle the loader response. when (response) { - is MusicStore.Response.Ok -> binding.homeShuffleFab.show() + is MusicStore.Response.Ok -> binding.homeFab.show() // While loading or during an error, make sure we keep the shuffle fab hidden so // that any kind of playback is impossible. PlaybackStateManager also relies on this // invariant, so please don't change it. - else -> binding.homeShuffleFab.hide() + else -> binding.homeFab.hide() } } @@ -186,9 +188,9 @@ class HomeFragment : Fragment() { } if (scrolling) { - binding.homeShuffleFab.hide() + binding.homeFab.hide() } else { - binding.homeShuffleFab.show() + binding.homeFab.show() } } diff --git a/app/src/main/res/drawable/ui_rounded_cutout.xml b/app/src/main/res/drawable/ui_rounded_cutout.xml deleted file mode 100644 index 00d969fd3..000000000 --- a/app/src/main/res/drawable/ui_rounded_cutout.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-h600dp/item_detail.xml b/app/src/main/res/layout-h600dp/item_detail.xml index 21e2f021a..02a1eff41 100644 --- a/app/src/main/res/layout-h600dp/item_detail.xml +++ b/app/src/main/res/layout-h600dp/item_detail.xml @@ -10,7 +10,7 @@ android:layout_height="match_parent" android:padding="@dimen/spacing_medium"> - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index f29dcde96..270cf530d 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,7 +1,6 @@ - 4dp 8dp 16dp 24dp @@ -17,6 +16,9 @@ 192dp 256dp + 8dp + 16dp + 32dp 32dp diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index ae84084c5..413acece4 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -26,32 +26,38 @@