Skip to content

Commit 562065d

Browse files
committed
Discord RPC
1 parent 39ed900 commit 562065d

39 files changed

+1511
-5
lines changed

app/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ dependencies {
146146
implementation(projects.innertube)
147147
implementation(projects.kugou)
148148
implementation(projects.lrclib)
149+
implementation(projects.kizzy)
149150

150151
coreLibraryDesugaring(libs.desugaring)
151152

app/proguard-rules.pro

+6-1
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,9 @@
6363
-dontwarn com.google.firebase.perf.network.FirebasePerfUrlConnection
6464
# opencc4j
6565
-keep class com.github.houbb.opencc4j.** { *; }
66-
-dontwarn com.huaban.analysis.jieba.JiebaSegmenter
66+
-dontwarn com.huaban.analysis.jieba.JiebaSegmenter
67+
68+
# Keep Data data classes
69+
-keep class com.my.kizzy.data.remote.** { <fields>; }
70+
# Keep Gateway data classes
71+
-keep class com.my.kizzy.gateway.entities.** { <fields>; }

app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ val PauseSearchHistoryKey = booleanPreferencesKey("pauseSearchHistory")
3535
val EnableKugouKey = booleanPreferencesKey("enableKugou")
3636
val EnableLrcLibKey = booleanPreferencesKey("enableLrcLib")
3737

38+
val DiscordTokenKey = stringPreferencesKey("discordToken")
39+
val DiscordInfoDismissedKey = booleanPreferencesKey("discordInfoDismissed")
40+
val DiscordUsernameKey = stringPreferencesKey("discordUsername")
41+
val DiscordNameKey = stringPreferencesKey("discordName")
42+
val EnableDiscordRPCKey = booleanPreferencesKey("discordRPCEnable")
43+
3844
val SongSortTypeKey = stringPreferencesKey("songSortType")
3945
val SongSortDescendingKey = booleanPreferencesKey("songSortDescending")
4046
val PlaylistSongSortTypeKey = stringPreferencesKey("playlistSongSortType")

app/src/main/java/com/zionhuang/music/playback/MusicService.kt

+32-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ import com.zionhuang.music.R
5959
import com.zionhuang.music.constants.AudioNormalizationKey
6060
import com.zionhuang.music.constants.AudioQuality
6161
import com.zionhuang.music.constants.AudioQualityKey
62+
import com.zionhuang.music.constants.DiscordTokenKey
63+
import com.zionhuang.music.constants.EnableDiscordRPCKey
6264
import com.zionhuang.music.constants.MediaSessionConstants.CommandToggleLibrary
6365
import com.zionhuang.music.constants.MediaSessionConstants.CommandToggleLike
6466
import com.zionhuang.music.constants.MediaSessionConstants.CommandToggleRepeatMode
@@ -92,6 +94,7 @@ import com.zionhuang.music.playback.queues.ListQueue
9294
import com.zionhuang.music.playback.queues.Queue
9395
import com.zionhuang.music.playback.queues.YouTubeQueue
9496
import com.zionhuang.music.utils.CoilBitmapLoader
97+
import com.zionhuang.music.utils.DiscordRPC
9598
import com.zionhuang.music.utils.dataStore
9699
import com.zionhuang.music.utils.enumPreference
97100
import com.zionhuang.music.utils.get
@@ -181,6 +184,8 @@ class MusicService : MediaLibraryService(),
181184

182185
private var isAudioEffectSessionOpened = false
183186

187+
private var discordRpc: DiscordRPC? = null
188+
184189
override fun onCreate() {
185190
super.onCreate()
186191
setMediaNotificationProvider(
@@ -245,8 +250,13 @@ class MusicService : MediaLibraryService(),
245250
}
246251
}
247252

248-
currentSong.collect(scope) {
253+
currentSong.debounce(1000).collect(scope) { song ->
249254
updateNotification()
255+
if (song != null) {
256+
discordRpc?.updateSong(song)
257+
} else {
258+
discordRpc?.closeRPC()
259+
}
250260
}
251261

252262
combine(
@@ -290,6 +300,23 @@ class MusicService : MediaLibraryService(),
290300
}
291301
}
292302

303+
dataStore.data
304+
.map { it[DiscordTokenKey] to (it[EnableDiscordRPCKey] ?: true) }
305+
.debounce(300)
306+
.distinctUntilChanged()
307+
.collect(scope) { (key, enabled) ->
308+
if (discordRpc?.isRpcRunning() == true) {
309+
discordRpc?.closeRPC()
310+
}
311+
discordRpc = null
312+
if (key != null && enabled) {
313+
discordRpc = DiscordRPC(this, key)
314+
currentSong.value?.let {
315+
discordRpc?.updateSong(it)
316+
}
317+
}
318+
}
319+
293320
if (dataStore.get(PersistentQueueKey, true)) {
294321
runCatching {
295322
filesDir.resolve(PERSISTENT_QUEUE_FILE).inputStream().use { fis ->
@@ -722,6 +749,10 @@ class MusicService : MediaLibraryService(),
722749
if (dataStore.get(PersistentQueueKey, true)) {
723750
saveQueueToDisk()
724751
}
752+
if (discordRpc?.isRpcRunning() == true) {
753+
discordRpc?.closeRPC()
754+
}
755+
discordRpc = null
725756
mediaSession.release()
726757
player.removeListener(this)
727758
player.removeListener(sleepTimer)

app/src/main/java/com/zionhuang/music/ui/component/Preference.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ fun PreferenceEntry(
3535
content: (@Composable () -> Unit)? = null,
3636
icon: (@Composable () -> Unit)? = null,
3737
trailingContent: (@Composable () -> Unit)? = null,
38-
onClick: () -> Unit,
38+
onClick: (() -> Unit)? = null,
3939
isEnabled: Boolean = true,
4040
) {
4141
Row(
4242
verticalAlignment = Alignment.CenterVertically,
4343
modifier = modifier
4444
.fillMaxWidth()
4545
.clickable(
46-
enabled = isEnabled,
47-
onClick = onClick
46+
enabled = isEnabled && onClick != null,
47+
onClick = onClick ?: {}
4848
)
4949
.alpha(if (isEnabled) 1f else 0.5f)
5050
.padding(horizontal = 16.dp, vertical = 16.dp)

app/src/main/java/com/zionhuang/music/ui/screens/NavigationBuilder.kt

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import com.zionhuang.music.ui.screens.settings.AboutScreen
2121
import com.zionhuang.music.ui.screens.settings.AppearanceSettings
2222
import com.zionhuang.music.ui.screens.settings.BackupAndRestore
2323
import com.zionhuang.music.ui.screens.settings.ContentSettings
24+
import com.zionhuang.music.ui.screens.settings.DiscordLoginScreen
25+
import com.zionhuang.music.ui.screens.settings.DiscordSettings
2426
import com.zionhuang.music.ui.screens.settings.PlayerSettings
2527
import com.zionhuang.music.ui.screens.settings.PrivacySettings
2628
import com.zionhuang.music.ui.screens.settings.SettingsScreen
@@ -181,6 +183,12 @@ fun NavGraphBuilder.navigationBuilder(
181183
composable("settings/backup_restore") {
182184
BackupAndRestore(navController, scrollBehavior)
183185
}
186+
composable("settings/discord") {
187+
DiscordSettings(navController, scrollBehavior)
188+
}
189+
composable("settings/discord/login") {
190+
DiscordLoginScreen(navController)
191+
}
184192
composable("settings/about") {
185193
AboutScreen(navController, scrollBehavior)
186194
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.zionhuang.music.ui.screens.settings
2+
3+
import android.annotation.SuppressLint
4+
import android.view.ViewGroup
5+
import android.webkit.CookieManager
6+
import android.webkit.JavascriptInterface
7+
import android.webkit.WebResourceRequest
8+
import android.webkit.WebStorage
9+
import android.webkit.WebView
10+
import android.webkit.WebViewClient
11+
import androidx.activity.compose.BackHandler
12+
import androidx.compose.foundation.layout.fillMaxSize
13+
import androidx.compose.foundation.layout.windowInsetsPadding
14+
import androidx.compose.material3.ExperimentalMaterial3Api
15+
import androidx.compose.material3.Icon
16+
import androidx.compose.material3.Text
17+
import androidx.compose.material3.TopAppBar
18+
import androidx.compose.runtime.Composable
19+
import androidx.compose.runtime.getValue
20+
import androidx.compose.runtime.rememberCoroutineScope
21+
import androidx.compose.runtime.setValue
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.res.painterResource
24+
import androidx.compose.ui.res.stringResource
25+
import androidx.compose.ui.viewinterop.AndroidView
26+
import androidx.navigation.NavController
27+
import com.zionhuang.music.LocalPlayerAwareWindowInsets
28+
import com.zionhuang.music.R
29+
import com.zionhuang.music.constants.DiscordNameKey
30+
import com.zionhuang.music.constants.DiscordTokenKey
31+
import com.zionhuang.music.constants.DiscordUsernameKey
32+
import com.zionhuang.music.ui.component.IconButton
33+
import com.zionhuang.music.ui.utils.backToMain
34+
import com.zionhuang.music.utils.rememberPreference
35+
36+
@SuppressLint("SetJavaScriptEnabled")
37+
@OptIn(ExperimentalMaterial3Api::class)
38+
@Composable
39+
fun DiscordLoginScreen(
40+
navController: NavController,
41+
) {
42+
val scope = rememberCoroutineScope()
43+
var discordToken by rememberPreference(DiscordTokenKey, "")
44+
var discordUsername by rememberPreference(DiscordUsernameKey, "")
45+
var discordName by rememberPreference(DiscordNameKey, "")
46+
47+
var webView: WebView? = null
48+
49+
AndroidView(
50+
modifier = Modifier
51+
.windowInsetsPadding(LocalPlayerAwareWindowInsets.current)
52+
.fillMaxSize(),
53+
factory = { context ->
54+
WebView(context).apply {
55+
layoutParams = ViewGroup.LayoutParams(
56+
ViewGroup.LayoutParams.MATCH_PARENT,
57+
ViewGroup.LayoutParams.MATCH_PARENT
58+
)
59+
60+
webViewClient = object : WebViewClient() {
61+
override fun shouldOverrideUrlLoading(
62+
webView: WebView,
63+
request: WebResourceRequest,
64+
): Boolean {
65+
stopLoading()
66+
if (request.url.toString().endsWith("/app")) {
67+
loadUrl("javascript:Android.onRetrieveToken((webpackChunkdiscord_app.push([[''],{},e=>{m=[];for(let c in e.c)m.push(e.c[c])}]),m).find(m=>m?.exports?.default?.getToken!==void 0).exports.default.getToken());")
68+
}
69+
return false
70+
}
71+
}
72+
settings.apply {
73+
javaScriptEnabled = true
74+
domStorageEnabled = true
75+
setSupportZoom(true)
76+
builtInZoomControls = true
77+
}
78+
val cookieManager = CookieManager.getInstance()
79+
cookieManager.removeAllCookies(null)
80+
cookieManager.flush()
81+
82+
WebStorage.getInstance().deleteAllData()
83+
addJavascriptInterface(object {
84+
@JavascriptInterface
85+
fun onRetrieveToken(token: String) {
86+
discordToken = token
87+
navController.navigateUp()
88+
}
89+
}, "Android")
90+
91+
webView = this
92+
loadUrl("https://discord.com/login")
93+
}
94+
}
95+
)
96+
97+
TopAppBar(
98+
title = { Text(stringResource(R.string.login)) },
99+
navigationIcon = {
100+
IconButton(
101+
onClick = navController::navigateUp,
102+
onLongClick = navController::backToMain
103+
) {
104+
Icon(
105+
painterResource(R.drawable.arrow_back),
106+
contentDescription = null
107+
)
108+
}
109+
}
110+
)
111+
112+
BackHandler(enabled = webView?.canGoBack() == true) {
113+
webView?.goBack()
114+
}
115+
}

0 commit comments

Comments
 (0)