mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Search page improvements (#709)
## Description Improves how search results are displayed: - Reorders search categories to Movies → TV Shows → Episodes → Collections, surfacing the most commonly searched content types at the top. - Adds a new "Combined search results" preference in Advanced settings → Search. When enabled, shows all results in a single list instead of separate categories. - When Seerr is active, adds Library/Discover tabs to switch between local library results and Seerr discovery results. - Hides empty result categories instead of showing "No results" placeholders. ### Screenshots <img width="1935" height="1100" alt="Screenshot_20260116_113656" src="https://github.com/user-attachments/assets/1e29aa3a-3ba2-4f86-ab61-fed683490474" /> <img width="1934" height="1095" alt="image" src="https://github.com/user-attachments/assets/8b93fa38-d4c9-4e72-a997-caa82b4949f0" /> <img width="1921" height="1094" alt="image" src="https://github.com/user-attachments/assets/82aed1a5-810c-413f-a266-6d78490a1534" /> ### AI/LLM usage Code auditing and PR description drafting with Claude Code. Also used Claude Code for the regex.
This commit is contained in:
parent
f364ef9708
commit
98ffaf51ac
6 changed files with 798 additions and 274 deletions
|
|
@ -66,7 +66,8 @@ class AppPreferencesSerializer
|
|||
directPlayPgs = AppPreference.DirectPlayPgs.defaultValue
|
||||
mediaExtensionsEnabled =
|
||||
AppPreference.FfmpegPreference.defaultValue
|
||||
assPlaybackMode = AppPreference.AssSubtitleMode.defaultValue
|
||||
assPlaybackMode =
|
||||
AppPreference.AssSubtitleMode.defaultValue
|
||||
}.build()
|
||||
|
||||
mpvOptions =
|
||||
|
|
@ -99,6 +100,13 @@ class AppPreferencesSerializer
|
|||
backdropStyle = AppPreference.BackdropStylePref.defaultValue
|
||||
showLogos = AppPreference.ShowLogos.defaultValue
|
||||
|
||||
searchPreferences =
|
||||
SearchPreferences
|
||||
.newBuilder()
|
||||
.apply {
|
||||
combinedSearchResults = false
|
||||
}.build()
|
||||
|
||||
subtitlesPreferences =
|
||||
SubtitlePreferences
|
||||
.newBuilder()
|
||||
|
|
@ -207,6 +215,11 @@ inline fun AppPreferences.updateInterfacePreferences(block: InterfacePreferences
|
|||
interfacePreferences = interfacePreferences.toBuilder().apply(block).build()
|
||||
}
|
||||
|
||||
inline fun AppPreferences.updateSearchPreferences(block: SearchPreferences.Builder.() -> Unit): AppPreferences =
|
||||
updateInterfacePreferences {
|
||||
searchPreferences = searchPreferences.toBuilder().apply(block).build()
|
||||
}
|
||||
|
||||
inline fun AppPreferences.updateSubtitlePreferences(block: SubtitlePreferences.Builder.() -> Unit): AppPreferences =
|
||||
updateInterfacePreferences {
|
||||
subtitlesPreferences = subtitlesPreferences.toBuilder().apply(block).build()
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ fun <T : CardGridItem> CardGrid(
|
|||
val cardWidth =
|
||||
ceil((width - (spacingPx * (columns - 1))) / columns)
|
||||
cardWidthPx = cardWidth.toInt()
|
||||
Timber.v("cardWidthPx=%s", cardWidthPx)
|
||||
// Timber.v("cardWidthPx=%s", cardWidthPx)
|
||||
},
|
||||
) {
|
||||
items(pager.size) { index ->
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,89 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import java.util.Locale
|
||||
|
||||
object SearchRelevance {
|
||||
private val REGEX_PATTERN = Regex("^/(.+)/(i)?$")
|
||||
|
||||
fun score(
|
||||
item: BaseItem,
|
||||
query: String,
|
||||
): Int {
|
||||
val name = item.name?.lowercase(Locale.getDefault()) ?: return Int.MAX_VALUE
|
||||
val q = query.lowercase(Locale.getDefault())
|
||||
|
||||
REGEX_PATTERN.find(query)?.let { match ->
|
||||
val pattern = match.groupValues[1]
|
||||
val caseInsensitive = match.groupValues[2] == "i"
|
||||
return regexScore(item.name ?: "", pattern, caseInsensitive) + typeBonus(item.type)
|
||||
}
|
||||
|
||||
return when {
|
||||
name == q -> 0
|
||||
name.startsWith(q) -> 100
|
||||
name.contains(" $q") -> 200
|
||||
name.contains(q) -> 300
|
||||
else -> fuzzyScore(name, q)
|
||||
} + typeBonus(item.type)
|
||||
}
|
||||
|
||||
private fun regexScore(
|
||||
name: String,
|
||||
pattern: String,
|
||||
caseInsensitive: Boolean,
|
||||
): Int =
|
||||
try {
|
||||
val options = if (caseInsensitive) setOf(RegexOption.IGNORE_CASE) else emptySet()
|
||||
if (Regex(pattern, options).containsMatchIn(name)) 250 else 400
|
||||
} catch (e: Exception) {
|
||||
400
|
||||
}
|
||||
|
||||
private fun fuzzyScore(
|
||||
name: String,
|
||||
query: String,
|
||||
): Int {
|
||||
val distance = levenshteinDistance(name, query)
|
||||
val maxLen = maxOf(name.length, query.length)
|
||||
val similarity = 1.0 - (distance.toDouble() / maxLen)
|
||||
|
||||
return when {
|
||||
similarity >= 0.8 -> 350
|
||||
similarity >= 0.6 -> 375
|
||||
else -> 400
|
||||
}
|
||||
}
|
||||
|
||||
private fun levenshteinDistance(
|
||||
s1: String,
|
||||
s2: String,
|
||||
): Int {
|
||||
val m = s1.length
|
||||
val n = s2.length
|
||||
val dp = Array(m + 1) { IntArray(n + 1) }
|
||||
for (i in 0..m) dp[i][0] = i
|
||||
for (j in 0..n) dp[0][j] = j
|
||||
for (i in 1..m) {
|
||||
for (j in 1..n) {
|
||||
dp[i][j] =
|
||||
if (s1[i - 1] == s2[j - 1]) {
|
||||
dp[i - 1][j - 1]
|
||||
} else {
|
||||
1 + minOf(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[m][n]
|
||||
}
|
||||
|
||||
private fun typeBonus(type: BaseItemKind): Int =
|
||||
when (type) {
|
||||
BaseItemKind.SERIES -> 0
|
||||
BaseItemKind.MOVIE -> 1
|
||||
BaseItemKind.BOX_SET -> 2
|
||||
BaseItemKind.EPISODE -> 10
|
||||
else -> 5
|
||||
}
|
||||
}
|
||||
|
|
@ -165,6 +165,10 @@ message ScreensaverPreferences{
|
|||
bool show_clock = 7;
|
||||
}
|
||||
|
||||
message SearchPreferences {
|
||||
bool combined_search_results = 1;
|
||||
}
|
||||
|
||||
message InterfacePreferences {
|
||||
ThemeSongVolume play_theme_songs = 1;
|
||||
bool remember_selected_tab = 2;
|
||||
|
|
@ -179,6 +183,7 @@ message InterfacePreferences {
|
|||
ScreensaverPreferences screensaver_preference = 11;
|
||||
bool enable_media_management = 12;
|
||||
bool show_logos = 13;
|
||||
SearchPreferences search_preferences = 14;
|
||||
}
|
||||
|
||||
message AdvancedPreferences {
|
||||
|
|
|
|||
|
|
@ -445,6 +445,10 @@
|
|||
<string name="color_code_programs">Color-code programs</string>
|
||||
<string name="subtitle_delay">Subtitle delay</string>
|
||||
<string name="backdrop_display">Backdrop style</string>
|
||||
<string name="combined_search_results">Combined search results</string>
|
||||
<string name="combined_search_results_on">Show all results in a single list</string>
|
||||
<string name="combined_search_results_off">Show results by category</string>
|
||||
<string name="results">Results</string>
|
||||
<string name="resolution_switching">Resolution switching</string>
|
||||
<string name="local">Local</string>
|
||||
<string name="play_trailer">Play trailer</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue