diff --git a/app/build.gradle b/app/build.gradle index 7c32fda8c..4350309db 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,7 +56,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3" // --- SUPPORT --- diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt index bb6a907ef..c98664870 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt @@ -55,11 +55,19 @@ val Long.audioUri: Uri val Long.albumCoverUri: Uri get() = ContentUris.withAppendedId(EXTERNAL_ALBUM_ART_URI, this) +/** + * Parse out the number field from a field assumed to be NN, where NN is a track number. This is + * most commonly found on vorbis comments. Values of zero will be ignored under the assumption that + * they are invalid. + */ +val String.trackNo: Int? + get() = toIntOrNull()?.let { if (it > 0) it else null } + /** * Parse out the number field from an NN/TT string that is typically found in DISC_NUMBER and * CD_TRACK_NUMBER. Values of zero will be ignored under the assumption that they are invalid. */ -val String.no: Int? +val String.trackDiscNo: Int? get() = split('/', limit = 2)[0].toIntOrNull()?.let { if (it > 0) it else null } /** diff --git a/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt index cad73113c..910e2a1d3 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt @@ -32,8 +32,13 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.util.lazyReflectedMethod import org.oxycblt.auxio.util.logEOrThrow +/** A path to a file. [name] is the stripped file name, [parent] is the parent path. */ data class Path(val name: String, val parent: Directory) +/** + * A path to a directory. [volume] is the volume the directory resides in, and [relativePath] is the + * path from the volume's root to the directory itself. + */ data class Directory(val volume: StorageVolume, val relativePath: String) { init { if (relativePath.startsWith(File.separatorChar) || diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt index 0af14cb9d..81d66eeb5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt @@ -29,7 +29,8 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.audioUri import org.oxycblt.auxio.music.id3GenreName import org.oxycblt.auxio.music.iso8601year -import org.oxycblt.auxio.music.no +import org.oxycblt.auxio.music.trackDiscNo +import org.oxycblt.auxio.music.trackNo import org.oxycblt.auxio.music.year import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW @@ -153,7 +154,9 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { return audio.toSong() } + // Populate the format mime type if we have one. format.sampleMimeType?.let { audio.formatMimeType = it } + val metadata = format.metadata if (metadata != null) { completeAudio(metadata) @@ -209,13 +212,13 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { tags["TIT2"]?.let { audio.title = it } // Track, as NN/TT - tags["TRCK"]?.no?.let { audio.track = it } + tags["TRCK"]?.trackDiscNo?.let { audio.track = it } // Disc, as NN/TT - tags["TPOS"]?.no?.let { audio.disc = it } + tags["TPOS"]?.trackDiscNo?.let { audio.disc = it } // Dates are somewhat complicated, as not only did their semantics change from a flat year - // value in ID3v2.3 to a full ISO-8601 date in ID3v2.4, but there are also a variety of + // value in ID3v2.3 to a full ISO-8601 date in ID3v2.4, but there are also a variety of // date types. // Our hierarchy for dates is as such: // 1. ID3v2.4 Original Date, as it resolves the "Released in X, Remastered in Y" issue @@ -223,11 +226,9 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { // 3. ID3v2.4 Release Date, as it is the second most common date type // 4. ID3v2.3 Original Date, as it is like #1 // 5. ID3v2.3 Release Year, as it is the most common date type - tags["TYER"]?.year?.let { audio.year = it } - tags["TORY"]?.year?.let { audio.year = it } - tags["TDRL"]?.iso8601year?.let { audio.year = it } - tags["TDRC"]?.iso8601year?.let { audio.year = it } - tags["TDOR"]?.iso8601year?.let { audio.year = it } + audio.year + ?: tags["TDOR"]?.iso8601year ?: tags["TDRC"]?.iso8601year ?: tags["TDRL"]?.iso8601year + ?: tags["TORY"]?.year ?: tags["TYER"]?.year // Album tags["TALB"]?.let { audio.album = it } @@ -246,11 +247,11 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { // Title tags["TITLE"]?.let { audio.title = it } - // Track, might be NN/TT, most often though TOTALTRACKS handles T. - tags["TRACKNUMBER"]?.no?.let { audio.track = it } + // Track. Probably not NN/TT, as TOTALTRACKS handles totals. + tags["TRACKNUMBER"]?.trackNo?.let { audio.track = it } - // Disc, might be NN/TT, most often though TOTALDISCS handles T. - tags["DISCNUMBER"]?.no?.let { audio.disc = it } + // Disc. Probably not NN/TT, as TOTALDISCS handles totals. + tags["DISCNUMBER"]?.trackNo?.let { audio.disc = it } // Vorbis dates are less complicated, but there are still several types // Our hierarchy for dates is as such: @@ -258,9 +259,8 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { // 2. Date, as it is the most common date type // 3. Year, as old vorbis tags tended to use this (I know this because it's the only // tag that android supports, so it must be 15 years old or more!) - tags["YEAR"]?.year?.let { audio.year = it } - tags["DATE"]?.iso8601year?.let { audio.year = it } - tags["ORIGINALDATE"]?.iso8601year?.let { audio.year = it } + audio.year = + tags["ORIGINALDATE"]?.iso8601year ?: tags["DATE"]?.iso8601year ?: tags["YEAR"]?.year // Album tags["ALBUM"]?.let { audio.album = it } @@ -271,8 +271,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { // Album artist. This actually comes into two flavors: // 1. ALBUMARTIST, which is the most common // 2. ALBUM ARTIST, which is present on older vorbis tags - tags["ALBUM ARTIST"]?.let { audio.albumArtist = it } - tags["ALBUMARTIST"]?.let { audio.albumArtist = it } + audio.albumArtist = tags["ALBUMARTIST"] ?: tags["ALBUM ARTIST"] // Genre, no ID3 rules here tags["GENRE"]?.let { audio.genre = it } diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt index 376c023f1..e3de299d5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt @@ -37,9 +37,9 @@ import org.oxycblt.auxio.music.audioUri import org.oxycblt.auxio.music.directoryCompat import org.oxycblt.auxio.music.id3GenreName import org.oxycblt.auxio.music.mediaStoreVolumeNameCompat -import org.oxycblt.auxio.music.no import org.oxycblt.auxio.music.queryCursor import org.oxycblt.auxio.music.storageVolumesCompat +import org.oxycblt.auxio.music.trackDiscNo import org.oxycblt.auxio.music.useQuery import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.contentResolverSafe @@ -72,8 +72,8 @@ import org.oxycblt.auxio.util.logD * the metadata parser has a brain aneurysm the moment it stumbles upon a dreaded TRDC or DATE tag. * Once again, this is because internally android uses an ancient in-house metadata parser to get * everything indexed, and so far they have not bothered to modernize this parser or even switch it - * to something more powerful like Taglib, not even in Android 12. ID3v2.4 has been around for *21 - * years.* *It can drink now.* All of my what. + * to something that actually works, not even in Android 12. ID3v2.4 has been around for *21 + * years.* *It can drink now.* * * Not to mention all the other infuriating quirks. Album artists can't be accessed from the albums * table, so we have to go for the less efficient "make a big query on all the songs lol" method so @@ -489,8 +489,7 @@ open class VolumeAwareMediaStoreBackend : MediaStoreBackend() { "AND ${MediaStore.Audio.AudioColumns.RELATIVE_PATH} LIKE ?)" override fun addDirToSelectorArgs(dir: Directory, args: MutableList): Boolean { - // Leverage the volume field when selecting our directories. It's a little too - // expensive to include this alongside the data checks, so we assume that + // Leverage new the volume field when selecting our directories. args.add(dir.volume.mediaStoreVolumeNameCompat ?: return false) args.add("${dir.relativePath}%") return true @@ -508,9 +507,8 @@ open class VolumeAwareMediaStoreBackend : MediaStoreBackend() { val volumeName = cursor.getString(volumeIndex) val relativePath = cursor.getString(relativePathIndex) - // We now have access to the volume name, so we try to leverage it instead. - // I have no idea how well this works in practice, but I assume that the fields - // probably exist. + // Find the StorageVolume whose MediaStore name corresponds to this song. + // This is what we use for the Directory. val volume = volumes.find { it.mediaStoreVolumeNameCompat == volumeName } if (volume != null) { audio.dir = Directory(volume, relativePath.removeSuffix(File.separator)) @@ -581,8 +579,8 @@ class Api30MediaStoreBackend : VolumeAwareMediaStoreBackend() { // N is the number and T is the total. Parse the number while leaving out the // total, as we have no use for it. - cursor.getStringOrNull(trackIndex)?.no?.let { audio.track = it } - cursor.getStringOrNull(discIndex)?.no?.let { audio.disc = it } + cursor.getStringOrNull(trackIndex)?.trackDiscNo?.let { audio.track = it } + cursor.getStringOrNull(discIndex)?.trackDiscNo?.let { audio.disc = it } return audio } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/NoRtlFrameLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/NoRtlFrameLayout.kt index a561f592a..42f66fa42 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/NoRtlFrameLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/NoRtlFrameLayout.kt @@ -30,6 +30,8 @@ import android.widget.FrameLayout * always be LTR. In Auxio, this applies to most of the playback components. This layout in * particular overrides the layout direction in a way that will not disrupt how other views are laid * out. + * + * @author OxygenCobalt */ open class NoRtlFrameLayout @JvmOverloads diff --git a/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPrefDialog.kt b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreferenceDialog.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPrefDialog.kt rename to app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreferenceDialog.kt index 0d1864680..0c1e85309 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPrefDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreferenceDialog.kt @@ -37,7 +37,6 @@ class IntListPreferenceDialog : PreferenceDialogFragmentCompat() { builder.setTitle(listPreference.title) builder.setPositiveButton(null, null) builder.setNegativeButton(R.string.lbl_cancel, null) - // TODO: Replace this with an in-house view builder.setSingleChoiceItems(listPreference.entries, listPreference.getValueIndex()) { _, index -> diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt index b34532216..f025d67e0 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt @@ -79,15 +79,15 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { songs.sortWith(mode.getSongComparator(isAscending)) } - fun albumsInPlace(albums: MutableList) { + private fun albumsInPlace(albums: MutableList) { albums.sortWith(mode.getAlbumComparator(isAscending)) } - fun artistsInPlace(artists: MutableList) { + private fun artistsInPlace(artists: MutableList) { artists.sortWith(mode.getArtistComparator(isAscending)) } - fun genresInPlace(genres: MutableList) { + private fun genresInPlace(genres: MutableList) { genres.sortWith(mode.getGenreComparator(isAscending)) } diff --git a/app/src/main/res/drawable/ic_dark.xml b/app/src/main/res/drawable/ic_dark.xml index e489c3028..6eb6eba85 100644 --- a/app/src/main/res/drawable/ic_dark.xml +++ b/app/src/main/res/drawable/ic_dark.xml @@ -2,10 +2,10 @@ - + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 000000000..012cddc6b --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/ui_scroll_thumb_compat.xml b/app/src/main/res/drawable/ui_scroll_thumb_compat.xml deleted file mode 100644 index 2e1b14cd1..000000000 --- a/app/src/main/res/drawable/ui_scroll_thumb_compat.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ui_track_compat.xml b/app/src/main/res/drawable/ui_track_compat.xml deleted file mode 100644 index b0ffc0e7e..000000000 --- a/app/src/main/res/drawable/ui_track_compat.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/font/inter.ttf b/app/src/main/res/font/inter.ttf deleted file mode 100644 index cc73944ac..000000000 Binary files a/app/src/main/res/font/inter.ttf and /dev/null differ diff --git a/app/src/main/res/font/inter_regular.otf b/app/src/main/res/font/inter_regular.otf new file mode 100644 index 000000000..84e6a61c3 Binary files /dev/null and b/app/src/main/res/font/inter_regular.otf differ diff --git a/app/src/main/res/font/inter_semibold.otf b/app/src/main/res/font/inter_semibold.otf new file mode 100644 index 000000000..daf4c4413 Binary files /dev/null and b/app/src/main/res/font/inter_semibold.otf differ diff --git a/app/src/main/res/font/inter_semibold.ttf b/app/src/main/res/font/inter_semibold.ttf deleted file mode 100644 index 278ceaa36..000000000 Binary files a/app/src/main/res/font/inter_semibold.ttf and /dev/null differ diff --git a/app/src/main/res/layout/dialog_pre_amp.xml b/app/src/main/res/layout/dialog_pre_amp.xml index 207359298..c73df9c7d 100644 --- a/app/src/main/res/layout/dialog_pre_amp.xml +++ b/app/src/main/res/layout/dialog_pre_amp.xml @@ -41,7 +41,7 @@ android:layout_marginEnd="@dimen/spacing_mid_large" android:gravity="center" android:minWidth="@dimen/size_pre_amp_ticker" - android:textAppearance="@style/TextAppearance.Auxio.BodyMedium" + android:textAppearance="@style/TextAppearance.Auxio.LabelMedium" app:layout_constraintBottom_toBottomOf="@+id/with_tags_slider" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/with_tags_slider" @@ -78,7 +78,7 @@ android:layout_marginEnd="@dimen/spacing_mid_large" android:gravity="center" android:minWidth="@dimen/size_pre_amp_ticker" - android:textAppearance="@style/TextAppearance.Auxio.BodyMedium" + android:textAppearance="@style/TextAppearance.Auxio.LabelMedium" app:layout_constraintBottom_toBottomOf="@+id/without_tags_slider" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/without_tags_slider" diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 25b6bc9da..9c410d8db 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -29,7 +29,7 @@ 24dp 32dp - 64dp + 56dp 16sp 18sp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c92e91aad..044a07584 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -106,7 +106,7 @@ ReplayGain Prefer track Prefer album - Dynamic + Prefer album if one is playing ReplayGain pre-amp The pre-amp is applied to the existing adjustment during playback Adjustment with tags @@ -114,8 +114,8 @@ Warning: Changing the pre-amp to a high positive value may result in peaking on some audio tracks. Behavior - Library playback mode - Detail playback mode + When playing from the library + When playing from item details Play from shown item Play from all songs Play from album diff --git a/app/src/main/res/values/typography.xml b/app/src/main/res/values/typography.xml index bd6511d27..0e3d625c8 100644 --- a/app/src/main/res/values/typography.xml +++ b/app/src/main/res/values/typography.xml @@ -17,16 +17,16 @@ @@ -43,14 +43,14 @@ @@ -65,15 +65,15 @@ @@ -87,16 +87,16 @@ @@ -104,22 +104,22 @@ The body typeface is used for secondary and/or singular UI elements. --> @@ -133,7 +133,7 @@ diff --git a/info/FAQ.md b/info/FAQ.md index dc56fb66a..c4c1b7538 100644 --- a/info/FAQ.md +++ b/info/FAQ.md @@ -73,10 +73,6 @@ ExoPlayer, while powerful, does add some overhead when playing exceptionally hig This results in choppy, distorted playback in some cases as audio data cannot be delivered in time. Sadly, there is not much I can do about this right now. -#### What is dynamic ReplayGain? -Dynamic ReplayGain is a quirk setting based off the FooBar2000 plugin that dynamically switches from track gain to album -gain depending on if the current playback is from an album or not. - #### Why are accents lighter/less saturated in dark mode? As per the [Material Design Guidelines](https://material.io/design/color/dark-theme.html), accents should be less saturated on dark mode to reduce eye strain and to increase visual cohesion.