Skip to content

Commit

Permalink
home: add last added sorting [#181]
Browse files Browse the repository at this point in the history
Add a "Last Added" sorting option to the home UI's song list.

I don't know if there is any demand for last added in other contexts.
That will be resolved later.
  • Loading branch information
OxygenCobalt committed Jul 13, 2022
1 parent 634fcb4 commit f014a2a
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 32 deletions.
2 changes: 2 additions & 0 deletions app/src/main/java/org/oxycblt/auxio/IntegerTable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ object IntegerTable {
const val SORT_BY_DISC = 0xA116
/** Sort.ByTrack */
const val SORT_BY_TRACK = 0xA117
/** Sort.ByDateAdded */
const val SORT_BY_DATE_ADDED = 0xA118

/** ReplayGainMode.Off */
const val REPLAY_GAIN_MODE_OFF = 0xA110
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/org/oxycblt/auxio/music/Music.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ data class Song(
val mimeType: MimeType,
/** The size of this song (in bytes) */
val size: Long,
/** The datetime at which this media item was added, represented as a unix timestamp. */
val dateAdded: Long,
/** The total duration of this song, in millis. */
val durationMs: Long,
/** The track number of this song, null if there isn't any. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
private var displayNameIndex = -1
private var mimeTypeIndex = -1
private var sizeIndex = -1
private var dateAddedIndex = -1
private var durationIndex = -1
private var yearIndex = -1
private var albumIndex = -1
Expand Down Expand Up @@ -235,7 +236,20 @@ abstract class MediaStoreBackend : Indexer.Backend {
* implementation.
*/
open val projection: Array<String>
get() = BASE_PROJECTION
get() =
arrayOf(
MediaStore.Audio.AudioColumns._ID,
MediaStore.Audio.AudioColumns.TITLE,
MediaStore.Audio.AudioColumns.DISPLAY_NAME,
MediaStore.Audio.AudioColumns.MIME_TYPE,
MediaStore.Audio.AudioColumns.SIZE,
MediaStore.Audio.AudioColumns.DATE_ADDED,
MediaStore.Audio.AudioColumns.DURATION,
MediaStore.Audio.AudioColumns.YEAR,
MediaStore.Audio.AudioColumns.ALBUM,
MediaStore.Audio.AudioColumns.ALBUM_ID,
MediaStore.Audio.AudioColumns.ARTIST,
AUDIO_COLUMN_ALBUM_ARTIST)

abstract val dirSelector: String
abstract fun addDirToSelectorArgs(dir: Directory, args: MutableList<String>): Boolean
Expand All @@ -254,6 +268,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
mimeTypeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.MIME_TYPE)
sizeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.SIZE)
dateAddedIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATE_ADDED)
durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DURATION)
yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR)
albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM)
Expand All @@ -269,6 +284,7 @@ abstract class MediaStoreBackend : Indexer.Backend {

audio.extensionMimeType = cursor.getString(mimeTypeIndex)
audio.size = cursor.getLong(sizeIndex)
audio.dateAdded = cursor.getLong(dateAddedIndex)

// Try to use the DISPLAY_NAME field to obtain a (probably sane) file name
// from the android system.
Expand Down Expand Up @@ -316,6 +332,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
var extensionMimeType: String? = null,
var formatMimeType: String? = null,
var size: Long? = null,
var dateAdded: Long? = null,
var duration: Long? = null,
var track: Int? = null,
var disc: Int? = null,
Expand All @@ -326,8 +343,8 @@ abstract class MediaStoreBackend : Indexer.Backend {
var albumArtist: String? = null,
var genre: String? = null
) {
fun toSong(): Song {
return Song(
fun toSong() =
Song(
// Assert that the fields that should always exist are present. I can't confirm that
// every device provides these fields, but it seems likely that they do.
rawName = requireNotNull(title) { "Malformed audio: No title" },
Expand All @@ -342,6 +359,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
requireNotNull(extensionMimeType) { "Malformed audio: No mime type" },
fromFormat = formatMimeType),
size = requireNotNull(size) { "Malformed audio: No size" },
dateAdded = requireNotNull(dateAdded) { "Malformed audio: No date added" },
durationMs = requireNotNull(duration) { "Malformed audio: No duration" },
track = track,
disc = disc,
Expand All @@ -352,7 +370,6 @@ abstract class MediaStoreBackend : Indexer.Backend {
_artistName = artist,
_albumArtistName = albumArtist,
_genreName = genre)
}
}

companion object {
Expand All @@ -371,24 +388,6 @@ abstract class MediaStoreBackend : Indexer.Backend {
*/
@Suppress("InlinedApi") private const val VOLUME_EXTERNAL = MediaStore.VOLUME_EXTERNAL

/**
* The basic projection that works across all versions of android. Is incomplete, hence why
* sub-implementations should be used instead.
*/
private val BASE_PROJECTION =
arrayOf(
MediaStore.Audio.AudioColumns._ID,
MediaStore.Audio.AudioColumns.TITLE,
MediaStore.Audio.AudioColumns.DISPLAY_NAME,
MediaStore.Audio.AudioColumns.MIME_TYPE,
MediaStore.Audio.AudioColumns.SIZE,
MediaStore.Audio.AudioColumns.DURATION,
MediaStore.Audio.AudioColumns.YEAR,
MediaStore.Audio.AudioColumns.ALBUM,
MediaStore.Audio.AudioColumns.ALBUM_ID,
MediaStore.Audio.AudioColumns.ARTIST,
AUDIO_COLUMN_ALBUM_ARTIST)

/**
* The base selector that works across all versions of android. Does not exclude
* directories.
Expand Down Expand Up @@ -418,6 +417,7 @@ class Api21MediaStoreBackend : MediaStoreBackend() {
get() = "${MediaStore.Audio.Media.DATA} LIKE ?"

override fun addDirToSelectorArgs(dir: Directory, args: MutableList<String>): Boolean {
// Generate an equivalent DATA value from the volume directory and the relative path.
args.add("${dir.volume.directoryCompat ?: return false}/${dir.relativePath}%")
return true
}
Expand All @@ -434,8 +434,9 @@ class Api21MediaStoreBackend : MediaStoreBackend() {
val data = cursor.getString(dataIndex)

// On some OEM devices below API 29, DISPLAY_NAME may not be present. I assume
// that this only applies to below API 29, as that would completely break the
// scoped storage system. Fill it in with DATA if it's not available.
// that this only applies to below API 29, as beyond API 29, this field not being
// present would completely break the scoped storage system. Fill it in with DATA
// if it's not available.
if (audio.displayName == null) {
audio.displayName = data.substringAfterLast(File.separatorChar, "").ifEmpty { null }
}
Expand All @@ -454,11 +455,7 @@ class Api21MediaStoreBackend : MediaStoreBackend() {

val rawTrack = cursor.getIntOrNull(trackIndex)
if (rawTrack != null) {
logD(rawTrack)
rawTrack.packedTrackNo?.let {
logD(it)
audio.track = it
}
rawTrack.packedTrackNo?.let { audio.track = it }
rawTrack.packedDiscNo?.let { audio.disc = it }
}

Expand Down Expand Up @@ -541,7 +538,7 @@ open class Api29MediaStoreBackend : BaseApi29MediaStoreBackend() {
}

// This backend is volume-aware, but does not support the modern track fields.
// Use the packed utilities instead.
// Use the old field instead.
val rawTrack = cursor.getIntOrNull(trackIndex)
if (rawTrack != null) {
rawTrack.packedTrackNo?.let { audio.track = it }
Expand Down
16 changes: 16 additions & 0 deletions app/src/main/java/org/oxycblt/auxio/ui/Sort.kt
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,27 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {

override val itemId: Int
get() = R.id.option_sort_disc

override fun getSongComparator(ascending: Boolean): Comparator<Song> =
MultiComparator(
compareByDynamic(ascending, NULLABLE_INT_COMPARATOR) { it.disc },
compareBy(NULLABLE_INT_COMPARATOR) { it.track },
compareBy(BasicComparator.SONG))
}

/** Sort by the time the item was added. Only supported by [Song] */
object ByDateAdded : Mode() {
override val intCode: Int
get() = IntegerTable.SORT_BY_DATE_ADDED

override val itemId: Int
get() = R.id.option_sort_date_added

override fun getSongComparator(ascending: Boolean): Comparator<Song> =
MultiComparator(
compareByDynamic(ascending) { it.dateAdded }, compareBy(BasicComparator.SONG))
}

/**
* Sort by the disc, and then track number of an item. Only supported by [Song]. Do not use
* this in a main sorting view, as it is not assigned to a particular item ID
Expand Down Expand Up @@ -374,6 +388,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
ByCount.itemId -> ByCount
ByDisc.itemId -> ByDisc
ByTrack.itemId -> ByTrack
ByDateAdded.itemId -> ByDateAdded
else -> null
}
}
Expand All @@ -398,6 +413,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
Mode.ByCount.intCode -> Mode.ByCount
Mode.ByDisc.intCode -> Mode.ByDisc
Mode.ByTrack.intCode -> Mode.ByTrack
Mode.ByDateAdded.intCode -> Mode.ByDateAdded
else -> return null
}

Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/org/oxycblt/auxio/util/PrimitiveUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ fun lazyReflectedMethod(clazz: KClass<*>, method: String) = lazy {
/**
* An abstraction that allows cheap cooperative multi-threading in shared object contexts. Every new
* task should call [newHandle], while every running task should call [check] or [yield] depending
* on the context to determine if it should continue.
* on the situation to determine if it should continue. Failure to follow the expectations of this
* class will result in bugs.
*
* @author OxygenCobalt
*/
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/menu/menu_home.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
<item
android:id="@+id/option_sort_count"
android:title="@string/lbl_sort_count" />
<item
android:id="@+id/option_sort_date_added"
android:title="@string/lbl_sort_date_added" />
</group>
<group android:checkableBehavior="all">
<item
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
<string name="lbl_sort_album">Album</string>
<string name="lbl_sort_year">Year</string>
<string name="lbl_sort_duration">Duration</string>
<string name="lbl_sort_count">Song Count</string>
<string name="lbl_sort_count">Song count</string>
<string name="lbl_sort_disc">Disc</string>
<string name="lbl_sort_track">Track</string>
<string name="lbl_sort_date_added">Date added</string>
<string name="lbl_sort_asc">Ascending</string>

<string name="lbl_playback">Now Playing</string>
Expand Down

0 comments on commit f014a2a

Please sign in to comment.