mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
fix lint and session access from the notification star review
- opt in to the Media3 unstable API on the service and answer unsupported custom commands with SessionError.ERROR_NOT_SUPPORTED - add the star command on top of the trust-aware connection defaults instead of granting untrusted controllers the full command set - number tracks within their disc, not across the whole album - leave the star button to the library flow so a failed write cannot leave the notification flipped - omit the leading cell entirely when a row has no art and no track
This commit is contained in:
@@ -22,6 +22,7 @@ import androidx.media3.session.CommandButton
|
|||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
import androidx.media3.session.MediaSessionService
|
import androidx.media3.session.MediaSessionService
|
||||||
import androidx.media3.session.SessionCommand
|
import androidx.media3.session.SessionCommand
|
||||||
|
import androidx.media3.session.SessionError
|
||||||
import androidx.media3.session.SessionResult
|
import androidx.media3.session.SessionResult
|
||||||
import coil3.SingletonImageLoader
|
import coil3.SingletonImageLoader
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
@@ -39,6 +40,7 @@ import kotlinx.coroutines.launch
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.koin.core.context.GlobalContext
|
import org.koin.core.context.GlobalContext
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
class PlaybackService : MediaSessionService() {
|
class PlaybackService : MediaSessionService() {
|
||||||
private var session: MediaSession? = null
|
private var session: MediaSession? = null
|
||||||
private var artworkLoader: CoverArtBitmapLoader? = null
|
private var artworkLoader: CoverArtBitmapLoader? = null
|
||||||
@@ -55,15 +57,17 @@ class PlaybackService : MediaSessionService() {
|
|||||||
override fun onConnect(
|
override fun onConnect(
|
||||||
session: MediaSession,
|
session: MediaSession,
|
||||||
controller: MediaSession.ControllerInfo,
|
controller: MediaSession.ControllerInfo,
|
||||||
): MediaSession.ConnectionResult =
|
): MediaSession.ConnectionResult {
|
||||||
MediaSession.ConnectionResult
|
val base = MediaSession.ConnectionResult.AcceptedResultBuilder(session, controller).build()
|
||||||
.AcceptedResultBuilder(session)
|
return MediaSession.ConnectionResult
|
||||||
|
.AcceptedResultBuilder(session, controller)
|
||||||
.setAvailableSessionCommands(
|
.setAvailableSessionCommands(
|
||||||
MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS
|
base.availableSessionCommands
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
.add(STAR_COMMAND)
|
.add(STAR_COMMAND)
|
||||||
.build(),
|
.build(),
|
||||||
).build()
|
).build()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCustomCommand(
|
override fun onCustomCommand(
|
||||||
session: MediaSession,
|
session: MediaSession,
|
||||||
@@ -72,7 +76,7 @@ class PlaybackService : MediaSessionService() {
|
|||||||
args: Bundle,
|
args: Bundle,
|
||||||
): ListenableFuture<SessionResult> {
|
): ListenableFuture<SessionResult> {
|
||||||
if (customCommand.customAction != STAR_COMMAND.customAction) {
|
if (customCommand.customAction != STAR_COMMAND.customAction) {
|
||||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
|
return Futures.immediateFuture(SessionResult(SessionError.ERROR_NOT_SUPPORTED))
|
||||||
}
|
}
|
||||||
toggleStar()
|
toggleStar()
|
||||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
@@ -163,8 +167,7 @@ class PlaybackService : MediaSessionService() {
|
|||||||
private fun toggleStar() {
|
private fun toggleStar() {
|
||||||
val songId = currentSongId ?: return
|
val songId = currentSongId ?: return
|
||||||
val library = library ?: return
|
val library = library ?: return
|
||||||
starred = !starred
|
library.star(StarType.Song, songId, !starred)
|
||||||
library.star(StarType.Song, songId, starred)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun starButton(isStarred: Boolean): CommandButton =
|
private fun starButton(isStarred: Boolean): CommandButton =
|
||||||
@@ -190,7 +193,6 @@ class PlaybackService : MediaSessionService() {
|
|||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
starJob?.cancel()
|
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
artworkLoader?.shutdown()
|
artworkLoader?.shutdown()
|
||||||
artworkLoader = null
|
artworkLoader = null
|
||||||
|
|||||||
@@ -132,6 +132,16 @@ fun AlbumDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val discOrdinals =
|
||||||
|
remember(songs) {
|
||||||
|
val counters = mutableMapOf<Long, Long>()
|
||||||
|
songs.associate { song ->
|
||||||
|
val disc = song.disc ?: 1L
|
||||||
|
val ordinal = (counters[disc] ?: 0L) + 1
|
||||||
|
counters[disc] = ordinal
|
||||||
|
song.id to ordinal
|
||||||
|
}
|
||||||
|
}
|
||||||
HeroDetailScaffold(
|
HeroDetailScaffold(
|
||||||
artwork = artwork,
|
artwork = artwork,
|
||||||
title = album?.name.orEmpty(),
|
title = album?.name.orEmpty(),
|
||||||
@@ -171,7 +181,7 @@ fun AlbumDetailScreen(
|
|||||||
SongRow(
|
SongRow(
|
||||||
song = song,
|
song = song,
|
||||||
isPlaying = song.id == playingSongId,
|
isPlaying = song.id == playingSongId,
|
||||||
trackNumber = song.track ?: (index + 1).toLong(),
|
trackNumber = song.track ?: discOrdinals[song.id],
|
||||||
durationSeconds = song.duration,
|
durationSeconds = song.duration,
|
||||||
download = downloads[song.id],
|
download = downloads[song.id],
|
||||||
modifier =
|
modifier =
|
||||||
|
|||||||
@@ -57,16 +57,24 @@ fun SongRow(
|
|||||||
ListItem(
|
ListItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
leadingContent =
|
leadingContent =
|
||||||
if (coverArt != null) {
|
when {
|
||||||
{
|
coverArt != null -> {
|
||||||
CoverArtCell(
|
{
|
||||||
ref = coverArt(coverArtId, true),
|
CoverArtCell(
|
||||||
name = song.album ?: song.title,
|
ref = coverArt(coverArtId, true),
|
||||||
isPlaying = isPlaying,
|
name = song.album ?: song.title,
|
||||||
)
|
isPlaying = isPlaying,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trackNumber != null || isPlaying -> {
|
||||||
|
{ TrackCell(trackNumber, isPlaying) }
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
{ TrackCell(trackNumber, isPlaying) }
|
|
||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
Reference in New Issue
Block a user