Skip to content

Commit 8b89ba2

Browse files
feat(discord): redesign settings UI and add customization options
- Completely redesigned Discord integration settings with Material 3 components - Added 'Advanced Mode' to toggle customization options (Status, Activity Type, Buttons, Activity Name) - Implemented template variable support for custom button text ({song_name}, {artist_name}, {album_name}) - Added token verification dialog with error feedback (prevents saving invalid tokens) - Improved Rich Presence preview with dynamic updates - Updated MusicService to support immediate RPC updates on setting changes - Added rate limit feedback (Toast) for manual configuration updates - Enhanced Dialog component with autoDismiss control to support verification flows - Updated KizzyRPC to fetch and utilize user avatar and ID
1 parent 8bd6726 commit 8b89ba2

File tree

9 files changed

+808
-192
lines changed

9 files changed

+808
-192
lines changed

app/src/main/kotlin/com/metrolist/music/constants/PreferenceKeys.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ val DiscordUsernameKey = stringPreferencesKey("discordUsername")
110110
val DiscordNameKey = stringPreferencesKey("discordName")
111111
val EnableDiscordRPCKey = booleanPreferencesKey("discordRPCEnable")
112112
val DiscordUseDetailsKey = booleanPreferencesKey("discordUseDetails")
113+
val DiscordAvatarKey = stringPreferencesKey("discordAvatar")
114+
val DiscordStatusKey = stringPreferencesKey("discordStatus")
115+
val DiscordButton1TextKey = stringPreferencesKey("discordButton1Text")
116+
val DiscordButton1VisibleKey = booleanPreferencesKey("discordButton1Visible")
117+
val DiscordButton2TextKey = stringPreferencesKey("discordButton2Text")
118+
val DiscordButton2VisibleKey = booleanPreferencesKey("discordButton2Visible")
119+
val DiscordActivityTypeKey = stringPreferencesKey("discordActivityType")
120+
val DiscordActivityNameKey = stringPreferencesKey("discordActivityName")
121+
val DiscordAdvancedModeKey = booleanPreferencesKey("discordAdvancedMode")
113122

114123
// Google Cast
115124
val EnableGoogleCastKey = booleanPreferencesKey("enableGoogleCast")

app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ import com.metrolist.music.constants.CrossfadeDurationKey
8585
import com.metrolist.music.constants.CrossfadeEnabledKey
8686
import com.metrolist.music.constants.CrossfadeGaplessKey
8787
import com.metrolist.music.constants.DisableLoadMoreWhenRepeatAllKey
88+
import android.os.Handler
89+
import android.os.Looper
90+
import android.widget.Toast
91+
import com.metrolist.music.constants.DiscordActivityNameKey
92+
import com.metrolist.music.constants.DiscordActivityTypeKey
93+
import com.metrolist.music.constants.DiscordAdvancedModeKey
94+
import com.metrolist.music.constants.DiscordAvatarKey
95+
import com.metrolist.music.constants.DiscordButton1TextKey
96+
import com.metrolist.music.constants.DiscordButton1VisibleKey
97+
import com.metrolist.music.constants.DiscordButton2TextKey
98+
import com.metrolist.music.constants.DiscordButton2VisibleKey
99+
import com.metrolist.music.constants.DiscordStatusKey
88100
import com.metrolist.music.constants.DiscordTokenKey
89101
import com.metrolist.music.constants.DiscordUseDetailsKey
90102
import com.metrolist.music.constants.EnableDiscordRPCKey
@@ -119,6 +131,7 @@ import com.metrolist.music.db.entities.Event
119131
import com.metrolist.music.db.entities.FormatEntity
120132
import com.metrolist.music.db.entities.LyricsEntity
121133
import com.metrolist.music.db.entities.RelatedSongMap
134+
import com.metrolist.music.db.entities.Song
122135
import com.metrolist.music.di.DownloadCache
123136
import com.metrolist.music.di.PlayerCache
124137
import com.metrolist.music.eq.EqualizerService
@@ -372,7 +385,7 @@ class MusicService :
372385
if (player.isPlaying) {
373386
scope.launch {
374387
currentSong.value?.let { song ->
375-
discordRpc?.updateSong(song, player.currentPosition, player.playbackParameters.speed, dataStore.get(DiscordUseDetailsKey, false))
388+
updateDiscordRPC(song)
376389
}
377390
}
378391
}
@@ -518,7 +531,7 @@ class MusicService :
518531
val mediaId = player.currentMetadata?.id
519532
if (mediaId != null) {
520533
database.song(mediaId).first()?.let { song ->
521-
discordRpc?.updateSong(song, player.currentPosition, player.playbackParameters.speed, dataStore.get(DiscordUseDetailsKey, false))
534+
updateDiscordRPC(song)
522535
}
523536
}
524537
}
@@ -620,25 +633,33 @@ class MusicService :
620633
discordRpc = DiscordRPC(this, key)
621634
if (player.playbackState == Player.STATE_READY && player.playWhenReady) {
622635
currentSong.value?.let {
623-
discordRpc?.updateSong(it, player.currentPosition, player.playbackParameters.speed, dataStore.get(DiscordUseDetailsKey, false))
636+
updateDiscordRPC(it, true)
624637
}
625638
}
626639
}
627640
}
628641

629-
// details key stuff
642+
// Watch all Discord customization preferences
630643
dataStore.data
631-
.map { it[DiscordUseDetailsKey] ?: false }
632-
.debounce(1000)
644+
.map {
645+
listOf(
646+
it[DiscordUseDetailsKey],
647+
it[DiscordAdvancedModeKey],
648+
it[DiscordStatusKey],
649+
it[DiscordButton1TextKey],
650+
it[DiscordButton1VisibleKey],
651+
it[DiscordButton2TextKey],
652+
it[DiscordButton2VisibleKey],
653+
it[DiscordActivityTypeKey],
654+
it[DiscordActivityNameKey]
655+
)
656+
}
657+
.debounce(300)
633658
.distinctUntilChanged()
634-
.collect(scope) { useDetails ->
635-
if (player.playbackState == Player.STATE_READY && player.playWhenReady) {
659+
.collect(scope) {
660+
if (player.playbackState == Player.STATE_READY) {
636661
currentSong.value?.let { song ->
637-
discordUpdateJob?.cancel()
638-
discordUpdateJob = scope.launch {
639-
delay(1000)
640-
discordRpc?.updateSong(song, player.currentPosition, player.playbackParameters.speed, useDetails)
641-
}
662+
updateDiscordRPC(song, true)
642663
}
643664
}
644665
}
@@ -1874,7 +1895,7 @@ class MusicService :
18741895
scope.launch {
18751896
// Fetch song from database to get full info
18761897
database.song(mediaId).first()?.let { song ->
1877-
discordRpc?.updateSong(song, player.currentPosition, player.playbackParameters.speed, dataStore.get(DiscordUseDetailsKey, false))
1898+
updateDiscordRPC(song)
18781899
}
18791900
}
18801901
}
@@ -1985,7 +2006,7 @@ class MusicService :
19852006
delay(1000)
19862007
if (player.playWhenReady && player.playbackState == Player.STATE_READY) {
19872008
currentSong.value?.let { song ->
1988-
discordRpc?.updateSong(song, player.currentPosition, playbackParameters.speed, dataStore.get(DiscordUseDetailsKey, false))
2009+
updateDiscordRPC(song)
19892010
}
19902011
}
19912012
}
@@ -2502,6 +2523,43 @@ class MusicService :
25022523
}
25032524
}
25042525

2526+
private fun updateDiscordRPC(song: Song, showFeedback: Boolean = false) {
2527+
val useDetails = dataStore.get(DiscordUseDetailsKey, false)
2528+
val advancedMode = dataStore.get(DiscordAdvancedModeKey, false)
2529+
2530+
val status = if (advancedMode) dataStore.get(DiscordStatusKey, "online") else "online"
2531+
val b1Text = if (advancedMode) dataStore.get(DiscordButton1TextKey, "") else ""
2532+
val b1Visible = if (advancedMode) dataStore.get(DiscordButton1VisibleKey, true) else true
2533+
val b2Text = if (advancedMode) dataStore.get(DiscordButton2TextKey, "") else ""
2534+
val b2Visible = if (advancedMode) dataStore.get(DiscordButton2VisibleKey, true) else true
2535+
val activityType = if (advancedMode) dataStore.get(DiscordActivityTypeKey, "listening") else "listening"
2536+
val activityName = if (advancedMode) dataStore.get(DiscordActivityNameKey, "") else ""
2537+
2538+
discordUpdateJob?.cancel()
2539+
discordUpdateJob = scope.launch {
2540+
discordRpc?.updateSong(
2541+
song,
2542+
player.currentPosition,
2543+
player.playbackParameters.speed,
2544+
useDetails,
2545+
status,
2546+
b1Text,
2547+
b1Visible,
2548+
b2Text,
2549+
b2Visible,
2550+
activityType,
2551+
activityName
2552+
)?.onFailure {
2553+
// Rate limited or error
2554+
if (showFeedback) {
2555+
Handler(Looper.getMainLooper()).post {
2556+
Toast.makeText(this@MusicService, "Discord RPC update failed: ${it.message}", Toast.LENGTH_SHORT).show()
2557+
}
2558+
}
2559+
}
2560+
}
2561+
}
2562+
25052563
private fun createDataSourceFactory(): DataSource.Factory {
25062564
return ResolvingDataSource.Factory(createCacheDataSource()) { dataSpec ->
25072565
val mediaId = dataSpec.key ?: error("No media id")

app/src/main/kotlin/com/metrolist/music/ui/component/Dialog.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ fun TextFieldDialog(
299299
onDoneMultiple: ((List<String>) -> Unit)? = null,
300300

301301
onDismiss: () -> Unit,
302+
autoDismiss: Boolean = true,
302303
extraContent: (@Composable () -> Unit)? = null,
303304
) {
304305
val legacyFieldState = remember { mutableStateOf(initialTextFieldValue) }
@@ -328,7 +329,7 @@ fun TextFieldDialog(
328329
TextButton(
329330
enabled = isValid,
330331
onClick = {
331-
onDismiss()
332+
if (autoDismiss) onDismiss()
332333
if (textFields != null && onDoneMultiple != null) {
333334
onDoneMultiple(textFields.map { it.second.text })
334335
} else {
@@ -356,14 +357,14 @@ fun TextFieldDialog(
356357
imeAction = if (singleLine) ImeAction.Done else ImeAction.None,
357358
keyboardType = keyboardType
358359
),
359-
keyboardActions = KeyboardActions(
360-
onDone = {
361-
if (onDoneMultiple != null) {
362-
onDoneMultiple(textFields.map { it.second.text })
363-
onDismiss()
364-
}
360+
keyboardActions = KeyboardActions(
361+
onDone = {
362+
if (onDoneMultiple != null) {
363+
onDoneMultiple(textFields.map { it.second.text })
364+
if (autoDismiss) onDismiss()
365365
}
366-
),
366+
}
367+
),
367368
modifier = Modifier
368369
.fillMaxWidth()
369370
.padding(bottom = if (index < textFields.size - 1) 12.dp else 0.dp)
@@ -385,7 +386,7 @@ fun TextFieldDialog(
385386
keyboardActions = KeyboardActions(
386387
onDone = {
387388
onDone(legacyFieldState.value.text)
388-
onDismiss()
389+
if (autoDismiss) onDismiss()
389390
}
390391
),
391392
modifier = Modifier

app/src/main/kotlin/com/metrolist/music/ui/screens/NavigationBuilder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ fun NavGraphBuilder.navigationBuilder(
353353
}
354354

355355
composable("settings/integrations/discord") {
356-
DiscordSettings(navController, scrollBehavior)
356+
DiscordSettings(navController, scrollBehavior, snackbarHostState)
357357
}
358358

359359
composable("settings/integrations/lastfm") {

0 commit comments

Comments
 (0)