feat(discord-UI): Re-UI logout button and improve thumbnail URL handling (#1253)
* feat(discord): re-UI the logout button * fix(discord): Add URL utility functions and improve thumbnail URL handling * refactor * Update core/common/src/main/kotlin/tachiyomi/core/common/util/system/UrlUtils.kt Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * fix(UrlUtils): Correct comments for URL scheme checks in utility functions * removing redundant checks * Remove redundant comment about URL scheme validation --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
This commit is contained in:
parent
0fa21a3de4
commit
98e4c24939
4 changed files with 149 additions and 6 deletions
|
|
@ -4,11 +4,14 @@ package eu.kanade.presentation.more.settings.screen
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Logout
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
|
|
@ -28,6 +31,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow
|
|||
import eu.kanade.domain.connections.service.ConnectionsPreferences
|
||||
import eu.kanade.presentation.category.visualName
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.tachiyomi.data.connections.ConnectionsManager
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
|
|
@ -35,6 +39,7 @@ import kotlinx.collections.immutable.toImmutableMap
|
|||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.kmk.KMR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
|
@ -224,9 +229,20 @@ object SettingsDiscordScreen : SearchableSettings {
|
|||
),
|
||||
),
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
Preference.PreferenceItem.CustomPreference(
|
||||
title = stringResource(MR.strings.logout),
|
||||
onClick = { dialog = LogoutConnectionDialog(connectionsManager.discord) },
|
||||
content = {
|
||||
TextPreferenceWidget(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = MaterialTheme.padding.large),
|
||||
title = stringResource(MR.strings.logout),
|
||||
icon = Icons.AutoMirrored.Filled.Logout,
|
||||
iconTint = MaterialTheme.colorScheme.error,
|
||||
onPreferenceClick = {
|
||||
dialog = LogoutConnectionDialog(connectionsManager.discord)
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -340,12 +340,20 @@ private fun onViewCreated(
|
|||
SYMR.strings.description_hint,
|
||||
manga.ogDescription?.takeIf { it.isNotBlank() }?.replace("\n", " ")?.chop(20) ?: "",
|
||||
)
|
||||
// KMK -->
|
||||
val thumbnailUrlHints = listOfNotNull(
|
||||
manga.ogThumbnailUrl?.let {
|
||||
it.chop(40) + if (it.length > 46) "." + it.substringAfterLast(".").chop(6) else ""
|
||||
},
|
||||
"file:///storage/emulated/0/Pictures/Komikku/Cover.jpg",
|
||||
)
|
||||
// KMK <--
|
||||
binding.thumbnailUrl.hint =
|
||||
context.stringResource(
|
||||
SYMR.strings.thumbnail_url_hint,
|
||||
manga.ogThumbnailUrl?.let {
|
||||
it.chop(40) + if (it.length > 46) "." + it.substringAfterLast(".").chop(6) else ""
|
||||
} ?: "",
|
||||
// KMK -->
|
||||
thumbnailUrlHints.joinToString("\nor\n"),
|
||||
// KMK <--
|
||||
)
|
||||
}
|
||||
binding.mangaGenresTags.clearFocus()
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ import tachiyomi.core.common.i18n.stringResource
|
|||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.core.common.util.system.UrlUtils
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.manga.model.MangaCover
|
||||
import tachiyomi.domain.manga.model.asMangaCover
|
||||
|
|
@ -1570,7 +1571,7 @@ class ReaderActivity : BaseActivity() {
|
|||
incognitoMode = viewModel.incognitoMode,
|
||||
mangaId = manga.id,
|
||||
mangaTitle = manga.ogTitle,
|
||||
thumbnailUrl = manga.thumbnailUrl,
|
||||
thumbnailUrl = manga.thumbnailUrl.takeIf { UrlUtils.isOnlineUrl(it) } ?: manga.ogThumbnailUrl,
|
||||
chapterNumber = if (connectionsPreferences.useChapterTitles().get()) {
|
||||
chapter.name
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
package tachiyomi.core.common.util.system
|
||||
|
||||
/**
|
||||
* Utility functions for URL handling and validation
|
||||
*/
|
||||
object UrlUtils {
|
||||
|
||||
/**
|
||||
* Detects if a URL string represents an online resource (not local storage)
|
||||
*
|
||||
* @param url The URL string to check
|
||||
* @return true if the URL is online, false if it's local storage or invalid
|
||||
*/
|
||||
fun isOnlineUrl(url: String?): Boolean {
|
||||
if (url.isNullOrBlank()) return false
|
||||
|
||||
val trimmedUrl = url.trim()
|
||||
|
||||
// Check for common online URL schemes
|
||||
return when {
|
||||
// Standard HTTP/HTTPS URLs
|
||||
trimmedUrl.startsWith("http://", ignoreCase = true) -> true
|
||||
trimmedUrl.startsWith("https://", ignoreCase = true) -> true
|
||||
|
||||
// FTP URLs (less common but still online)
|
||||
trimmedUrl.startsWith("ftp://", ignoreCase = true) -> true
|
||||
trimmedUrl.startsWith("ftps://", ignoreCase = true) -> true
|
||||
|
||||
// If none of the above patterns match, assume it's not a valid local URL
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if a URL string represents a local storage resource
|
||||
*
|
||||
* @param url The URL string to check
|
||||
* @return true if the URL is local storage, false if it's online or invalid
|
||||
*/
|
||||
fun isLocalUrl(url: String?): Boolean {
|
||||
if (url.isNullOrBlank()) return false
|
||||
|
||||
val trimmedUrl = url.trim()
|
||||
|
||||
// Check for local storage URL schemes
|
||||
return when {
|
||||
// Local file schemes
|
||||
trimmedUrl.startsWith("file://", ignoreCase = true) -> true
|
||||
trimmedUrl.startsWith("content://", ignoreCase = true) -> true
|
||||
trimmedUrl.startsWith("android_asset://", ignoreCase = true) -> true
|
||||
|
||||
// Intent URLs (Android specific)
|
||||
trimmedUrl.startsWith("intent://", ignoreCase = true) -> true
|
||||
|
||||
// Local paths (absolute or relative)
|
||||
trimmedUrl.startsWith("/") -> true
|
||||
trimmedUrl.startsWith("./") -> true
|
||||
trimmedUrl.startsWith("../") -> true
|
||||
|
||||
// If none of the above patterns match, assume it's not a valid embedded URL
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if a URL string represents an embedded resource (such as blob or data URLs)
|
||||
*
|
||||
* @param url The URL string to check
|
||||
* @return true if the URL is an embedded resource, false otherwise
|
||||
*/
|
||||
fun isEmbeddedUrl(url: String?): Boolean {
|
||||
if (url.isNullOrBlank()) return false
|
||||
|
||||
val trimmedUrl = url.trim()
|
||||
|
||||
// Check for embedded URL schemes
|
||||
return when {
|
||||
// Blob URLs (typically local/temporary)
|
||||
trimmedUrl.startsWith("blob:", ignoreCase = true) -> true
|
||||
|
||||
// Data URLs (embedded data)
|
||||
trimmedUrl.startsWith("data:", ignoreCase = true) -> true
|
||||
|
||||
// If none of the above patterns match, assume it's not a valid online URL
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scheme (protocol) from a URL string
|
||||
*
|
||||
* @param url The URL string
|
||||
* @return The scheme part of the URL, or null if not found
|
||||
*/
|
||||
fun getUrlScheme(url: String?): String? {
|
||||
if (url.isNullOrBlank()) return null
|
||||
|
||||
// Find the first colon, which separates the scheme from the rest
|
||||
val colonIndex = url.indexOf(':')
|
||||
if (colonIndex <= 0) return null
|
||||
|
||||
return url.substring(0, colonIndex).lowercase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a URL is a valid web URL (HTTP or HTTPS)
|
||||
*
|
||||
* @param url The URL string to check
|
||||
* @return true if it's a valid web URL, false otherwise
|
||||
*/
|
||||
fun isWebUrl(url: String?): Boolean {
|
||||
if (url.isNullOrBlank()) return false
|
||||
|
||||
val trimmedUrl = url.trim()
|
||||
return trimmedUrl.startsWith("http://", ignoreCase = true) ||
|
||||
trimmedUrl.startsWith("https://", ignoreCase = true)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue