update required stubs

This commit is contained in:
tapframe 2026-05-12 13:56:36 +05:30
parent fdde8ba1c2
commit 632d30c6c8
12 changed files with 204 additions and 9 deletions

View file

@ -367,6 +367,19 @@ tasks.matching { it.name == "packageReleaseDistributionForCurrentOS" || it.name
finalizedBy(renameReleaseDmgArtifact) finalizedBy(renameReleaseDmgArtifact)
} }
val buildDesktopMpvBridge = tasks.register<Exec>("buildDesktopMpvBridge") {
onlyIf { System.getProperty("os.name").contains("Mac", ignoreCase = true) }
workingDir = rootProject.file("MPVKit")
commandLine("swift", "build", "-c", "release", "--product", "DesktopMPVBridge")
inputs.file(rootProject.file("MPVKit/Package.swift"))
inputs.dir(rootProject.file("MPVKit/Sources/DesktopMPVBridge"))
outputs.dir(rootProject.file("MPVKit/.build"))
}
tasks.matching { it.name == "run" || it.name == "desktopRun" }.configureEach {
dependsOn(buildDesktopMpvBridge)
}
configurations.all { configurations.all {
exclude(group = "androidx.media3", module = "media3-exoplayer") exclude(group = "androidx.media3", module = "media3-exoplayer")
exclude(group = "androidx.media3", module = "media3-ui") exclude(group = "androidx.media3", module = "media3-ui")

View file

@ -0,0 +1,18 @@
package com.nuvio.app.core.ui
internal actual fun isLiquidGlassNativeTabBarSupported(): Boolean = false
internal actual fun publishLiquidGlassNativeTabBarEnabled(enabled: Boolean) = Unit
internal actual fun publishNativeTabBarVisible(visible: Boolean) = Unit
internal actual fun publishNativeSelectedTab(tabName: String) = Unit
internal actual fun publishNativeTabAccentColor(hexColor: String) = Unit
internal actual fun publishNativeProfileTabIcon(
name: String?,
avatarColorHex: String?,
avatarImageUrl: String?,
avatarBackgroundColorHex: String?,
) = Unit

View file

@ -35,6 +35,11 @@ private val addonHttpClient: HttpClient = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL) .followRedirects(HttpClient.Redirect.NORMAL)
.build() .build()
private val addonHttpClientNoRedirects: HttpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(60))
.followRedirects(HttpClient.Redirect.NEVER)
.build()
private const val maxRawResponseBodyChars = 1024 * 1024 private const val maxRawResponseBodyChars = 1024 * 1024
private const val truncationSuffix = "\n...[truncated]" private const val truncationSuffix = "\n...[truncated]"
@ -54,6 +59,7 @@ private suspend fun executeRequest(
url: String, url: String,
headers: Map<String, String>, headers: Map<String, String>,
body: String, body: String,
followRedirects: Boolean = true,
) = withContext(Dispatchers.IO) { ) = withContext(Dispatchers.IO) {
val builder = HttpRequest.newBuilder() val builder = HttpRequest.newBuilder()
.uri(URI.create(url)) .uri(URI.create(url))
@ -69,7 +75,8 @@ private suspend fun executeRequest(
builder.method(method.uppercase(), HttpRequest.BodyPublishers.noBody()) builder.method(method.uppercase(), HttpRequest.BodyPublishers.noBody())
}.build() }.build()
addonHttpClient.send(request, HttpResponse.BodyHandlers.ofString()) val client = if (followRedirects) addonHttpClient else addonHttpClientNoRedirects
client.send(request, HttpResponse.BodyHandlers.ofString())
} }
private suspend fun executeTextRequest( private suspend fun executeTextRequest(
@ -137,8 +144,9 @@ actual suspend fun httpRequestRaw(
url: String, url: String,
headers: Map<String, String>, headers: Map<String, String>,
body: String, body: String,
followRedirects: Boolean,
): RawHttpResponse { ): RawHttpResponse {
val response = executeRequest(method, url, headers, body) val response = executeRequest(method, url, headers, body, followRedirects)
val payload = response.body() val payload = response.body()
val limitedPayload = if (payload.length > maxRawResponseBodyChars) { val limitedPayload = if (payload.length > maxRawResponseBodyChars) {
payload.take(maxRawResponseBodyChars) + truncationSuffix payload.take(maxRawResponseBodyChars) + truncationSuffix

View file

@ -0,0 +1,16 @@
package com.nuvio.app.features.collection
import com.nuvio.app.core.storage.ProfileScopedKey
import com.nuvio.app.desktop.DesktopPreferences
internal actual object CollectionMobileSettingsStorage {
private const val preferencesName = "nuvio_collection_mobile_settings"
private const val payloadKey = "collection_mobile_settings_payload"
actual fun loadPayload(): String? =
DesktopPreferences.getString(preferencesName, ProfileScopedKey.of(payloadKey))
actual fun savePayload(payload: String) {
DesktopPreferences.putString(preferencesName, ProfileScopedKey.of(payloadKey), payload)
}
}

View file

@ -2,6 +2,8 @@ package com.nuvio.app.features.downloads
import com.nuvio.app.core.storage.ProfileScopedKey import com.nuvio.app.core.storage.ProfileScopedKey
import com.nuvio.app.desktop.DesktopPreferences import com.nuvio.app.desktop.DesktopPreferences
import java.io.File
import java.net.URI
internal actual object DownloadsStorage { internal actual object DownloadsStorage {
private const val preferencesName = "nuvio_downloads" private const val preferencesName = "nuvio_downloads"
@ -33,8 +35,20 @@ internal actual object DownloadsPlatformDownloader {
actual fun removeFile(localFileUri: String?): Boolean = false actual fun removeFile(localFileUri: String?): Boolean = false
actual fun removePartialFile(destinationFileName: String): Boolean = false actual fun removePartialFile(destinationFileName: String): Boolean = false
actual fun resolveLocalFileUri(localFileUri: String?, destinationFileName: String): String? =
localFileUri
?.toLocalFileOrNull()
?.takeIf { it.exists() }
?.toURI()
?.toString()
} }
private fun String.toLocalFileOrNull(): File? =
runCatching {
if (startsWith("file://")) File(URI(this)) else File(this)
}.getOrNull()
internal actual object DownloadsLiveStatusPlatform { internal actual object DownloadsLiveStatusPlatform {
actual fun onItemsChanged(items: List<DownloadItem>) = Unit actual fun onItemsChanged(items: List<DownloadItem>) = Unit
} }

View file

@ -466,6 +466,8 @@ internal actual object PlayerSettingsStorage {
private const val skipIntroEnabledKey = "skip_intro_enabled" private const val skipIntroEnabledKey = "skip_intro_enabled"
private const val animeSkipEnabledKey = "animeskip_enabled" private const val animeSkipEnabledKey = "animeskip_enabled"
private const val animeSkipClientIdKey = "animeskip_client_id" private const val animeSkipClientIdKey = "animeskip_client_id"
private const val introDbApiKeyKey = "introdb_api_key"
private const val introSubmitEnabledKey = "intro_submit_enabled"
private const val streamAutoPlayNextEpisodeEnabledKey = "stream_auto_play_next_episode_enabled" private const val streamAutoPlayNextEpisodeEnabledKey = "stream_auto_play_next_episode_enabled"
private const val streamAutoPlayPreferBingeGroupKey = "stream_auto_play_prefer_binge_group" private const val streamAutoPlayPreferBingeGroupKey = "stream_auto_play_prefer_binge_group"
private const val nextEpisodeThresholdModeKey = "next_episode_threshold_mode" private const val nextEpisodeThresholdModeKey = "next_episode_threshold_mode"
@ -496,6 +498,8 @@ internal actual object PlayerSettingsStorage {
skipIntroEnabledKey, skipIntroEnabledKey,
animeSkipEnabledKey, animeSkipEnabledKey,
animeSkipClientIdKey, animeSkipClientIdKey,
introDbApiKeyKey,
introSubmitEnabledKey,
streamAutoPlayNextEpisodeEnabledKey, streamAutoPlayNextEpisodeEnabledKey,
streamAutoPlayPreferBingeGroupKey, streamAutoPlayPreferBingeGroupKey,
nextEpisodeThresholdModeKey, nextEpisodeThresholdModeKey,
@ -661,6 +665,18 @@ internal actual object PlayerSettingsStorage {
saveString(animeSkipClientIdKey, clientId) saveString(animeSkipClientIdKey, clientId)
} }
actual fun loadIntroDbApiKey(): String? = loadString(introDbApiKeyKey)
actual fun saveIntroDbApiKey(apiKey: String) {
saveString(introDbApiKeyKey, apiKey)
}
actual fun loadIntroSubmitEnabled(): Boolean? = loadBoolean(introSubmitEnabledKey)
actual fun saveIntroSubmitEnabled(enabled: Boolean) {
saveBoolean(introSubmitEnabledKey, enabled)
}
actual fun loadStreamAutoPlayNextEpisodeEnabled(): Boolean? = loadBoolean(streamAutoPlayNextEpisodeEnabledKey) actual fun loadStreamAutoPlayNextEpisodeEnabled(): Boolean? = loadBoolean(streamAutoPlayNextEpisodeEnabledKey)
actual fun saveStreamAutoPlayNextEpisodeEnabled(enabled: Boolean) { actual fun saveStreamAutoPlayNextEpisodeEnabled(enabled: Boolean) {
@ -726,6 +742,8 @@ internal actual object PlayerSettingsStorage {
loadSkipIntroEnabled()?.let { put(skipIntroEnabledKey, encodeSyncBoolean(it)) } loadSkipIntroEnabled()?.let { put(skipIntroEnabledKey, encodeSyncBoolean(it)) }
loadAnimeSkipEnabled()?.let { put(animeSkipEnabledKey, encodeSyncBoolean(it)) } loadAnimeSkipEnabled()?.let { put(animeSkipEnabledKey, encodeSyncBoolean(it)) }
loadAnimeSkipClientId()?.let { put(animeSkipClientIdKey, encodeSyncString(it)) } loadAnimeSkipClientId()?.let { put(animeSkipClientIdKey, encodeSyncString(it)) }
loadIntroDbApiKey()?.let { put(introDbApiKeyKey, encodeSyncString(it)) }
loadIntroSubmitEnabled()?.let { put(introSubmitEnabledKey, encodeSyncBoolean(it)) }
loadStreamAutoPlayNextEpisodeEnabled()?.let { put(streamAutoPlayNextEpisodeEnabledKey, encodeSyncBoolean(it)) } loadStreamAutoPlayNextEpisodeEnabled()?.let { put(streamAutoPlayNextEpisodeEnabledKey, encodeSyncBoolean(it)) }
loadStreamAutoPlayPreferBingeGroup()?.let { put(streamAutoPlayPreferBingeGroupKey, encodeSyncBoolean(it)) } loadStreamAutoPlayPreferBingeGroup()?.let { put(streamAutoPlayPreferBingeGroupKey, encodeSyncBoolean(it)) }
loadNextEpisodeThresholdMode()?.let { put(nextEpisodeThresholdModeKey, encodeSyncString(it)) } loadNextEpisodeThresholdMode()?.let { put(nextEpisodeThresholdModeKey, encodeSyncString(it)) }
@ -760,6 +778,8 @@ internal actual object PlayerSettingsStorage {
payload.decodeSyncBoolean(skipIntroEnabledKey)?.let(::saveSkipIntroEnabled) payload.decodeSyncBoolean(skipIntroEnabledKey)?.let(::saveSkipIntroEnabled)
payload.decodeSyncBoolean(animeSkipEnabledKey)?.let(::saveAnimeSkipEnabled) payload.decodeSyncBoolean(animeSkipEnabledKey)?.let(::saveAnimeSkipEnabled)
payload.decodeSyncString(animeSkipClientIdKey)?.let(::saveAnimeSkipClientId) payload.decodeSyncString(animeSkipClientIdKey)?.let(::saveAnimeSkipClientId)
payload.decodeSyncString(introDbApiKeyKey)?.let(::saveIntroDbApiKey)
payload.decodeSyncBoolean(introSubmitEnabledKey)?.let(::saveIntroSubmitEnabled)
payload.decodeSyncBoolean(streamAutoPlayNextEpisodeEnabledKey)?.let(::saveStreamAutoPlayNextEpisodeEnabled) payload.decodeSyncBoolean(streamAutoPlayNextEpisodeEnabledKey)?.let(::saveStreamAutoPlayNextEpisodeEnabled)
payload.decodeSyncBoolean(streamAutoPlayPreferBingeGroupKey)?.let(::saveStreamAutoPlayPreferBingeGroup) payload.decodeSyncBoolean(streamAutoPlayPreferBingeGroupKey)?.let(::saveStreamAutoPlayPreferBingeGroup)
payload.decodeSyncString(nextEpisodeThresholdModeKey)?.let(::saveNextEpisodeThresholdMode) payload.decodeSyncString(nextEpisodeThresholdModeKey)?.let(::saveNextEpisodeThresholdMode)
@ -819,7 +839,7 @@ internal actual object PlayerSettingsStorage {
actual fun LockPlayerToLandscape() = Unit actual fun LockPlayerToLandscape() = Unit
@Composable @Composable
actual fun EnterImmersivePlayerMode() = Unit actual fun EnterImmersivePlayerMode(keepScreenAwake: Boolean) = Unit
@Composable @Composable
actual fun ManagePlayerPictureInPicture( actual fun ManagePlayerPictureInPicture(

View file

@ -0,0 +1,15 @@
package com.nuvio.app.features.profiles
import com.nuvio.app.desktop.DesktopPreferences
internal actual object AvatarStorage {
private const val preferencesName = "nuvio_avatar_cache"
private const val payloadKey = "avatar_catalog_payload"
actual fun loadPayload(): String? =
DesktopPreferences.getString(preferencesName, payloadKey)
actual fun savePayload(payload: String) {
DesktopPreferences.putString(preferencesName, payloadKey, payload)
}
}

View file

@ -0,0 +1,7 @@
package com.nuvio.app.features.profiles
internal actual object ProfileHoverHapticFeedback {
actual fun prepare() = Unit
actual fun perform() = Unit
actual fun release() = Unit
}

View file

@ -0,0 +1,20 @@
package com.nuvio.app.features.profiles
import com.nuvio.app.desktop.DesktopPreferences
internal actual object ProfilePinCacheStorage {
private const val preferencesName = "nuvio_profile_pin_cache"
actual fun loadPayload(profileIndex: Int): String? =
DesktopPreferences.getString(preferencesName, payloadKey(profileIndex))
actual fun savePayload(profileIndex: Int, payload: String) {
DesktopPreferences.putString(preferencesName, payloadKey(profileIndex), payload)
}
actual fun removePayload(profileIndex: Int) {
DesktopPreferences.remove(preferencesName, payloadKey(profileIndex))
}
private fun payloadKey(profileIndex: Int): String = "profile_pin_cache_$profileIndex"
}

View file

@ -0,0 +1,12 @@
package com.nuvio.app.features.profiles
import java.security.MessageDigest
internal actual object ProfilePinCrypto {
actual fun sha256Hex(value: String): String {
val digest = MessageDigest.getInstance("SHA-256").digest(value.encodeToByteArray())
return digest.joinToString(separator = "") { byte ->
byte.toUByte().toString(16).padStart(2, '0')
}
}
}

View file

@ -13,6 +13,7 @@ import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put import kotlinx.serialization.json.put
import nuvio.composeapp.generated.resources.Res import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.introdb_favicon
import nuvio.composeapp.generated.resources.mdblist_logo import nuvio.composeapp.generated.resources.mdblist_logo
import nuvio.composeapp.generated.resources.rating_tmdb import nuvio.composeapp.generated.resources.rating_tmdb
import nuvio.composeapp.generated.resources.trakt_tv_favicon import nuvio.composeapp.generated.resources.trakt_tv_favicon
@ -22,7 +23,14 @@ internal actual object ThemeSettingsStorage {
private const val preferencesName = "nuvio_theme_settings" private const val preferencesName = "nuvio_theme_settings"
private const val selectedThemeKey = "selected_theme" private const val selectedThemeKey = "selected_theme"
private const val amoledEnabledKey = "amoled_enabled" private const val amoledEnabledKey = "amoled_enabled"
private val syncKeys = listOf(selectedThemeKey, amoledEnabledKey) private const val liquidGlassNativeTabBarEnabledKey = "liquid_glass_native_tab_bar_enabled"
private const val selectedAppLanguageKey = "selected_app_language"
private val profileScopedSyncKeys = listOf(
selectedThemeKey,
amoledEnabledKey,
liquidGlassNativeTabBarEnabledKey,
)
private val globalSyncKeys = listOf(selectedAppLanguageKey)
actual fun loadSelectedTheme(): String? = actual fun loadSelectedTheme(): String? =
DesktopPreferences.getString(preferencesName, ProfileScopedKey.of(selectedThemeKey)) DesktopPreferences.getString(preferencesName, ProfileScopedKey.of(selectedThemeKey))
@ -38,16 +46,43 @@ internal actual object ThemeSettingsStorage {
DesktopPreferences.putBoolean(preferencesName, ProfileScopedKey.of(amoledEnabledKey), enabled) DesktopPreferences.putBoolean(preferencesName, ProfileScopedKey.of(amoledEnabledKey), enabled)
} }
actual fun loadLiquidGlassNativeTabBarEnabled(): Boolean? =
DesktopPreferences.getBoolean(preferencesName, ProfileScopedKey.of(liquidGlassNativeTabBarEnabledKey))
actual fun saveLiquidGlassNativeTabBarEnabled(enabled: Boolean) {
DesktopPreferences.putBoolean(preferencesName, ProfileScopedKey.of(liquidGlassNativeTabBarEnabledKey), enabled)
}
actual fun loadSelectedAppLanguage(): String? {
val value = DesktopPreferences.getString(preferencesName, selectedAppLanguageKey)
if (value != null) return value
val legacy = DesktopPreferences.getString(preferencesName, ProfileScopedKey.of(selectedAppLanguageKey))
if (legacy != null) saveSelectedAppLanguage(legacy)
return legacy
}
actual fun saveSelectedAppLanguage(languageCode: String) {
DesktopPreferences.putString(preferencesName, selectedAppLanguageKey, languageCode)
}
actual fun applySelectedAppLanguage(languageCode: String) = Unit
actual fun exportToSyncPayload(): JsonObject = buildJsonObject { actual fun exportToSyncPayload(): JsonObject = buildJsonObject {
loadSelectedTheme()?.let { put(selectedThemeKey, encodeSyncString(it)) } loadSelectedTheme()?.let { put(selectedThemeKey, encodeSyncString(it)) }
loadAmoledEnabled()?.let { put(amoledEnabledKey, encodeSyncBoolean(it)) } loadAmoledEnabled()?.let { put(amoledEnabledKey, encodeSyncBoolean(it)) }
loadLiquidGlassNativeTabBarEnabled()?.let { put(liquidGlassNativeTabBarEnabledKey, encodeSyncBoolean(it)) }
loadSelectedAppLanguage()?.let { put(selectedAppLanguageKey, encodeSyncString(it)) }
} }
actual fun replaceFromSyncPayload(payload: JsonObject) { actual fun replaceFromSyncPayload(payload: JsonObject) {
syncKeys.forEach { DesktopPreferences.remove(preferencesName, ProfileScopedKey.of(it)) } profileScopedSyncKeys.forEach { DesktopPreferences.remove(preferencesName, ProfileScopedKey.of(it)) }
globalSyncKeys.forEach { DesktopPreferences.remove(preferencesName, it) }
payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme) payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme)
payload.decodeSyncBoolean(amoledEnabledKey)?.let(::saveAmoledEnabled) payload.decodeSyncBoolean(amoledEnabledKey)?.let(::saveAmoledEnabled)
payload.decodeSyncBoolean(liquidGlassNativeTabBarEnabledKey)?.let(::saveLiquidGlassNativeTabBarEnabled)
payload.decodeSyncString(selectedAppLanguageKey)?.let(::saveSelectedAppLanguage)
applySelectedAppLanguage(loadSelectedAppLanguage() ?: AppLanguage.ENGLISH.code)
} }
} }
@ -59,4 +94,5 @@ internal actual fun integrationLogoPainter(logo: IntegrationLogo): Painter =
IntegrationLogo.Tmdb -> painterResource(Res.drawable.rating_tmdb) IntegrationLogo.Tmdb -> painterResource(Res.drawable.rating_tmdb)
IntegrationLogo.Trakt -> painterResource(Res.drawable.trakt_tv_favicon) IntegrationLogo.Trakt -> painterResource(Res.drawable.trakt_tv_favicon)
IntegrationLogo.MdbList -> painterResource(Res.drawable.mdblist_logo) IntegrationLogo.MdbList -> painterResource(Res.drawable.mdblist_logo)
IntegrationLogo.IntroDb -> painterResource(Res.drawable.introdb_favicon)
} }

View file

@ -0,0 +1,16 @@
package com.nuvio.app.features.trakt
import com.nuvio.app.core.storage.ProfileScopedKey
import com.nuvio.app.desktop.DesktopPreferences
internal actual object TraktSettingsStorage {
private const val preferencesName = "nuvio_trakt_settings"
private const val payloadKey = "trakt_settings_payload"
actual fun loadPayload(): String? =
DesktopPreferences.getString(preferencesName, ProfileScopedKey.of(payloadKey))
actual fun savePayload(payload: String) {
DesktopPreferences.putString(preferencesName, ProfileScopedKey.of(payloadKey), payload)
}
}