home: add last added sorting [#181]
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.
This commit is contained in:
parent
634fcb4273
commit
f014a2a48d
7 changed files with 54 additions and 32 deletions
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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" },
|
||||
|
@ -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,
|
||||
|
@ -353,7 +371,6 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
|||
_albumArtistName = albumArtist,
|
||||
_genreName = genre)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
|
|
@ -252,6 +252,7 @@ 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 },
|
||||
|
@ -259,6 +260,19 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
|||
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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue