chore(discord): Enhance Discord integration and update application ID (#1236)

* fix(discord): Update Komikku image

* feat(discord): Enhance Discord connection preferences with login state and improved UI

* fix(discord): Update application ID for Discord Rich Presence
This commit is contained in:
Cuong-Tran 2025-10-13 14:38:27 +07:00 committed by GitHub
parent d15fa402df
commit 317a21adc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 54 additions and 25 deletions

View file

@ -164,9 +164,9 @@ sealed class Preference {
override val title: String, override val title: String,
val login: () -> Unit, val login: () -> Unit,
val openSettings: () -> Unit, val openSettings: () -> Unit,
override val subtitle: String? = null,
) : PreferenceItem<String>() { ) : PreferenceItem<String>() {
override val enabled: Boolean = true override val enabled: Boolean = true
override val subtitle: String? = null
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (newValue: String) -> Boolean = { true } override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
} }

View file

@ -176,13 +176,13 @@ internal fun PreferenceItem(
} }
// AM (CONNECTIONS) --> // AM (CONNECTIONS) -->
is Preference.PreferenceItem.ConnectionPreference -> { is Preference.PreferenceItem.ConnectionPreference -> {
item.service.run { val isLoggedIn by item.service.isLoggedInFlow.collectAsState(item.service.isLogged)
ConnectionPreferenceWidget( ConnectionPreferenceWidget(
service = this, service = item.service,
checked = isLogged, checked = isLoggedIn,
onClick = { if (isLogged) item.openSettings() else item.login() }, onClick = { if (isLoggedIn) item.openSettings() else item.login() },
) subtitle = item.subtitle,
} )
} }
// <-- AM (CONNECTIONS) // <-- AM (CONNECTIONS)
is Preference.PreferenceItem.InfoPreference -> { is Preference.PreferenceItem.InfoPreference -> {

View file

@ -22,6 +22,7 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -80,23 +81,21 @@ object SettingsConnectionScreen : SearchableSettings {
} }
} }
val isLoggedIn by connectionsManager.discord.isLoggedInFlow.collectAsState(connectionsManager.discord.isLogged)
return listOf( return listOf(
Preference.PreferenceGroup( Preference.PreferenceGroup(
title = stringResource(KMR.strings.special_services), title = stringResource(KMR.strings.special_services),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.ConnectionPreference( Preference.PreferenceItem.ConnectionPreference(
title = stringResource(connectionsManager.discord.nameStrRes()), title = stringResource(connectionsManager.discord.nameStrRes()),
subtitle = stringResource(KMR.strings.pref_discord_configuration).takeIf { isLoggedIn },
service = connectionsManager.discord, service = connectionsManager.discord,
login = { login = {
navigator.push(DiscordLoginScreen()) navigator.push(DiscordLoginScreen())
}, },
openSettings = { navigator.push(SettingsDiscordScreen) }, openSettings = { navigator.push(SettingsDiscordScreen) },
), ),
Preference.PreferenceItem.TextPreference(
title = stringResource(KMR.strings.pref_discord_configuration),
enabled = connectionsManager.discord.isLogged,
onClick = { navigator.push(SettingsDiscordScreen) },
),
Preference.PreferenceItem.InfoPreference( Preference.PreferenceItem.InfoPreference(
stringResource(KMR.strings.connections_discord_info, stringResource(MR.strings.app_name)), stringResource(KMR.strings.connections_discord_info, stringResource(MR.strings.app_name)),
), ),

View file

@ -3,10 +3,12 @@ package eu.kanade.presentation.more.settings.widget
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Done import androidx.compose.material.icons.outlined.Done
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -19,37 +21,54 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.connection.components.ConnectionLogoIcon import eu.kanade.presentation.connection.components.ConnectionLogoIcon
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
import eu.kanade.tachiyomi.data.connections.ConnectionsService import eu.kanade.tachiyomi.data.connections.ConnectionsService
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable @Composable
@Suppress("ModifierNotUsedAtRoot", "MagicNumber") @Suppress("ModifierNotUsedAtRoot", "MagicNumber")
fun ConnectionPreferenceWidget( fun ConnectionPreferenceWidget(
service: ConnectionsService, service: ConnectionsService,
checked: Boolean, checked: Boolean,
subtitle: String?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
) { ) {
val highlighted = LocalPreferenceHighlighted.current val highlighted = LocalPreferenceHighlighted.current
val minHeight = LocalPreferenceMinHeight.current
Box(modifier = Modifier.highlightBackground(highlighted)) { Box(modifier = Modifier.highlightBackground(highlighted)) {
Row( Row(
modifier = modifier modifier = modifier
.sizeIn(minHeight = minHeight)
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() }) .clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = PrefsHorizontalPadding, vertical = 8.dp), .padding(horizontal = PrefsHorizontalPadding),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
ConnectionLogoIcon(service) ConnectionLogoIcon(service)
Text( Column(
text = stringResource(service.nameStrRes()),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(horizontal = 16.dp), .padding(horizontal = PrefsHorizontalPadding),
maxLines = 1, ) {
style = MaterialTheme.typography.titleLarge, Text(
fontSize = TitleFontSize, text = stringResource(service.nameStrRes()),
) maxLines = 1,
style = MaterialTheme.typography.titleLarge,
fontSize = TitleFontSize,
)
if (!subtitle.isNullOrBlank()) {
Text(
text = subtitle,
modifier = Modifier
.secondaryItemAlpha(),
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
)
}
}
if (checked) { if (checked) {
Icon( Icon(
imageVector = Icons.Outlined.Done, imageVector = Icons.Outlined.Done,

View file

@ -7,6 +7,8 @@ import androidx.annotation.DrawableRes
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.connections.service.ConnectionsPreferences import eu.kanade.domain.connections.service.ConnectionsPreferences
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -52,6 +54,15 @@ abstract class ConnectionsService(val id: Long) {
get() = getUsername().isNotEmpty() && get() = getUsername().isNotEmpty() &&
getPassword().isNotEmpty() getPassword().isNotEmpty()
open val isLoggedInFlow: Flow<Boolean> by lazy {
combine(
connectionsPreferences.connectionsUsername(this).changes(),
connectionsPreferences.connectionsPassword(this).changes(),
) { username, password ->
username.isNotEmpty() && password.isNotEmpty()
}
}
/** /**
* Override this method in token-based services to retrieve the token. * Override this method in token-based services to retrieve the token.
* Default implementation returns an empty string. * Default implementation returns an empty string.

View file

@ -17,7 +17,7 @@ import kotlinx.serialization.json.JsonElement
const val RICH_PRESENCE_TAG = "discord_rpc" const val RICH_PRESENCE_TAG = "discord_rpc"
// Constant for application id // Constant for application id
internal const val RICH_PRESENCE_APPLICATION_ID = "1365208874440986685" internal const val RICH_PRESENCE_APPLICATION_ID = "1424627741256585271"
val DOWNLOAD_BUTTON_LABEL_RES = R.string.discord_download_button val DOWNLOAD_BUTTON_LABEL_RES = R.string.discord_download_button
const val DOWNLOAD_BUTTON_URL = "https://komikku-app.github.io/download/" const val DOWNLOAD_BUTTON_URL = "https://komikku-app.github.io/download/"
@ -197,8 +197,8 @@ enum class DiscordScreen(
} }
// Constants for standard Rich Presence image urls // Constants for standard Rich Presence image urls
private const val KOMIKKU_IMAGE_URL = "emojis/1401719615536500916.webp?quality=lossless" private const val KOMIKKU_IMAGE_URL = "emojis/1365538288894738532.webp?quality=lossless"
private const val KOMIKKU_PREVIEW_IMAGE_URL = "emojis/1401732831314575401.webp?quality=lossless" private const val KOMIKKU_PREVIEW_IMAGE_URL = "emojis/1365538288894738532.webp?quality=lossless"
private val KOMIKKU_IMAGE = if (isPreviewBuildType) KOMIKKU_PREVIEW_IMAGE_URL else KOMIKKU_IMAGE_URL private val KOMIKKU_IMAGE = if (isPreviewBuildType) KOMIKKU_PREVIEW_IMAGE_URL else KOMIKKU_IMAGE_URL
private const val LIBRARY_IMAGE_URL = "emojis/1365262809050644591.webp?quality=lossless" private const val LIBRARY_IMAGE_URL = "emojis/1365262809050644591.webp?quality=lossless"

View file

@ -181,7 +181,7 @@
<string name="special_services">Special Services</string> <string name="special_services">Special Services</string>
<string name="pref_category_connections">Connections</string> <string name="pref_category_connections">Connections</string>
<string name="pref_connections_summary">Discord, more to come..</string> <string name="pref_connections_summary">Discord, more to come..</string>
<string name="pref_discord_configuration">Configure Discord RPC</string> <string name="pref_discord_configuration">Open to configure Discord RPC</string>
<string name="connections_discord">Discord</string> <string name="connections_discord">Discord</string>
<string name="connections_discord_info">This Discord Service works through token logging. Use at your own risk. Your token is stored locally and %s does not share it anywhere else</string> <string name="connections_discord_info">This Discord Service works through token logging. Use at your own risk. Your token is stored locally and %s does not share it anywhere else</string>
<string name="pref_discord_rpc">Discord Rich Presence</string> <string name="pref_discord_rpc">Discord Rich Presence</string>