diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index 3877c26c..9cb740ab 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -93,9 +93,6 @@ class MainActivity : AppCompatActivity() { @Inject lateinit var updateChecker: UpdateChecker - @Inject - lateinit var appUpgradeHandler: AppUpgradeHandler - @Inject lateinit var playbackLifecycleObserver: PlaybackLifecycleObserver @@ -136,11 +133,7 @@ class MainActivity : AppCompatActivity() { instance = this Timber.i("MainActivity.onCreate: savedInstanceState is null=${savedInstanceState == null}") lifecycle.addObserver(playbackLifecycleObserver) - if (savedInstanceState == null) { - lifecycleScope.launchIO { - appUpgradeHandler.copySubfont(false) - } - } + viewModel.serverRepository.currentUser.observe(this) { user -> if (user?.hasPin == true) { window?.setFlags( @@ -249,7 +242,6 @@ class MainActivity : AppCompatActivity() { Timber.d("onResume") lifecycleScope.launchDefault { screensaverService.pulse() - appUpgradeHandler.run() } } @@ -384,10 +376,13 @@ class MainActivityViewModel private val navigationManager: SetupNavigationManager, private val deviceProfileService: DeviceProfileService, private val backdropService: BackdropService, + private val appUpgradeHandler: AppUpgradeHandler, ) : ViewModel() { fun appStart() { viewModelScope.launchIO { try { + appUpgradeHandler.run() + appUpgradeHandler.copySubfont(false) val prefs = preferences.data.firstOrNull() ?: AppPreferences.getDefaultInstance() val userHasPin = serverRepository.currentUser.value?.hasPin == true diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt index 3144b014..0cf88ff1 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt @@ -3,25 +3,16 @@ package com.github.damontecres.wholphin.data.model import androidx.compose.runtime.Stable -import com.github.damontecres.wholphin.api.seerr.model.CreditCast -import com.github.damontecres.wholphin.api.seerr.model.CreditCrew -import com.github.damontecres.wholphin.api.seerr.model.MovieDetails import com.github.damontecres.wholphin.api.seerr.model.MovieMovieIdRatingsGet200Response -import com.github.damontecres.wholphin.api.seerr.model.MovieResult -import com.github.damontecres.wholphin.api.seerr.model.TvDetails -import com.github.damontecres.wholphin.api.seerr.model.TvResult import com.github.damontecres.wholphin.api.seerr.model.TvTvIdRatingsGet200Response -import com.github.damontecres.wholphin.services.SeerrSearchResult import com.github.damontecres.wholphin.ui.detail.CardGridItem import com.github.damontecres.wholphin.ui.nav.Destination -import com.github.damontecres.wholphin.ui.toLocalDate import com.github.damontecres.wholphin.util.LocalDateSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.serializer.UUIDSerializer -import org.jellyfin.sdk.model.serializer.toUUIDOrNull import java.time.LocalDate import java.util.UUID @@ -85,17 +76,14 @@ data class DiscoverItem( val overview: String?, val availability: SeerrAvailability, @Serializable(LocalDateSerializer::class) val releaseDate: LocalDate?, - val posterPath: String?, - val backdropPath: String?, + val posterUrl: String?, + val backDropUrl: String?, val jellyfinItemId: UUID?, ) : CardGridItem { override val gridId: String get() = id.toString() override val playable: Boolean = false override val sortName: String get() = title ?: "" - val backDropUrl: String? get() = backdropPath?.let { "https://image.tmdb.org/t/p/w1920_and_h1080_multi_faces$it" } - val posterUrl: String? get() = posterPath?.let { "https://image.tmdb.org/t/p/w500$it" } - val destination: Destination get() { val jfType = @@ -114,103 +102,6 @@ data class DiscoverItem( Destination.DiscoveredItem(this) } } - - constructor(movie: MovieResult) : this( - id = movie.id, - type = SeerrItemType.MOVIE, - title = movie.title, - subtitle = null, - overview = movie.overview, - availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, - releaseDate = toLocalDate(movie.releaseDate), - posterPath = movie.posterPath, - backdropPath = movie.backdropPath, - jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), - ) - - constructor(movie: MovieDetails) : this( - id = movie.id ?: -1, - type = SeerrItemType.MOVIE, - title = movie.title, - subtitle = null, - overview = movie.overview, - availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, - releaseDate = toLocalDate(movie.releaseDate), - posterPath = movie.posterPath, - backdropPath = movie.backdropPath, - jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), - ) - - constructor(tv: TvResult) : this( - id = tv.id!!, - type = SeerrItemType.TV, - title = tv.name, - subtitle = null, - overview = tv.overview, - availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, - releaseDate = toLocalDate(tv.firstAirDate), - posterPath = tv.posterPath, - backdropPath = tv.backdropPath, - jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), - ) - - constructor(tv: TvDetails) : this( - id = tv.id!!, - type = SeerrItemType.TV, - title = tv.name, - subtitle = null, - overview = tv.overview, - availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, - releaseDate = toLocalDate(tv.firstAirDate), - posterPath = tv.posterPath, - backdropPath = tv.backdropPath, - jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), - ) - - constructor(search: SeerrSearchResult) : this( - id = search.id, - type = SeerrItemType.fromString(search.mediaType), - title = search.title ?: search.name, - subtitle = null, - overview = search.overview, - availability = - SeerrAvailability.from(search.mediaInfo?.status) - ?: SeerrAvailability.UNKNOWN, - releaseDate = toLocalDate(search.releaseDate ?: search.firstAirDate), - posterPath = search.posterPath, - backdropPath = search.backdropPath, - jellyfinItemId = search.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), - ) - - constructor(credit: CreditCast) : this( - id = credit.id!!, - type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON), - title = credit.name ?: credit.title, - subtitle = credit.character, - overview = credit.overview, - availability = - SeerrAvailability.from(credit.mediaInfo?.status) - ?: SeerrAvailability.UNKNOWN, - releaseDate = toLocalDate(credit.firstAirDate), - posterPath = credit.posterPath ?: credit.profilePath, - backdropPath = credit.backdropPath, - jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), - ) - - constructor(credit: CreditCrew) : this( - id = credit.id!!, - type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON), - title = credit.name ?: credit.title, - subtitle = credit.job, - overview = credit.overview, - availability = - SeerrAvailability.from(credit.mediaInfo?.status) - ?: SeerrAvailability.UNKNOWN, - releaseDate = toLocalDate(credit.firstAirDate), - posterPath = credit.posterPath ?: credit.profilePath, - backdropPath = credit.backdropPath, - jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), - ) } data class DiscoverRating( diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt b/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt index e6c554cb..617944fc 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt @@ -6,6 +6,7 @@ import androidx.core.content.edit import androidx.datastore.core.DataStore import androidx.preference.PreferenceManager import com.github.damontecres.wholphin.WholphinApplication +import com.github.damontecres.wholphin.data.SeerrServerDao import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.ScreensaverPreference @@ -22,6 +23,7 @@ import com.github.damontecres.wholphin.preferences.updateScreensaverPreferences import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings +import com.github.damontecres.wholphin.ui.setup.seerr.migrateSeerrUrl import com.github.damontecres.wholphin.util.Version import dagger.hilt.android.qualifiers.ApplicationContext import org.jellyfin.sdk.model.api.BaseItemKind @@ -36,9 +38,14 @@ class AppUpgradeHandler constructor( @param:ApplicationContext private val context: Context, private val appPreferences: DataStore, + private val seerrServerDao: SeerrServerDao, ) { suspend fun run() { - val pkgInfo = WholphinApplication.instance.packageManager.getPackageInfo(WholphinApplication.instance.packageName, 0) + val pkgInfo = + WholphinApplication.instance.packageManager.getPackageInfo( + WholphinApplication.instance.packageName, + 0, + ) val prefs = PreferenceManager.getDefaultSharedPreferences(context) val previousVersion = prefs.getString(VERSION_NAME_CURRENT_KEY, null) val previousVersionCode = prefs.getLong(VERSION_CODE_CURRENT_KEY, -1) @@ -63,14 +70,16 @@ class AppUpgradeHandler try { copySubfont(true) upgradeApp( - context, - Version.Companion.fromString(previousVersion ?: "0.0.0"), - Version.Companion.fromString(newVersion), + Version.fromString(previousVersion ?: "0.0.0"), + Version.fromString(newVersion), appPreferences, ) } catch (ex: Exception) { Timber.e(ex, "Exception during app upgrade") } + Timber.i("App upgrade complete") + } else { + Timber.d("No app update needed") } } @@ -101,110 +110,108 @@ class AppUpgradeHandler const val VERSION_NAME_CURRENT_KEY = "version.current.name" const val VERSION_CODE_CURRENT_KEY = "version.current.code" } - } -suspend fun upgradeApp( - context: Context, - previous: Version, - current: Version, - appPreferences: DataStore, -) { - if (previous.isEqualOrBefore(Version.fromString("0.1.0-2-g0"))) { - appPreferences.updateData { - it.updatePlaybackOverrides { - ac3Supported = AppPreference.Ac3Supported.defaultValue - downmixStereo = AppPreference.DownMixStereo.defaultValue - directPlayAss = AppPreference.DirectPlayAss.defaultValue - directPlayPgs = AppPreference.DirectPlayPgs.defaultValue - } - } - } - if (previous.isEqualOrBefore(Version.fromString("0.2.3-6-g0"))) { - appPreferences.updateData { - it.updateInterfacePreferences { - navDrawerSwitchOnFocus = AppPreference.NavDrawerSwitchOnFocus.defaultValue - } - } - } - if (previous.isEqualOrBefore(Version.fromString("0.2.5-11-g0"))) { - appPreferences.updateData { - it.updateInterfacePreferences { - showClock = AppPreference.ShowClock.defaultValue - } - } - } - if (previous.isEqualOrBefore(Version.fromString("0.2.7-1-g0"))) { - PreferencesViewModel.resetSubtitleSettings(appPreferences) - } - if (previous.isEqualOrBefore(Version.fromString("0.3.2-4-g0"))) { - appPreferences.updateData { - it.updateSubtitlePreferences { - margin = SubtitleSettings.Margin.defaultValue.toInt() - } - } - } - - if (previous.isEqualOrBefore(Version.fromString("0.3.4"))) { - appPreferences.updateData { - it.updateAdvancedPreferences { - if (imageDiskCacheSizeBytes < (AppPreference.ImageDiskCacheSize.min * AppPreference.MEGA_BIT)) { - imageDiskCacheSizeBytes = - AppPreference.ImageDiskCacheSize.defaultValue * AppPreference.MEGA_BIT + suspend fun upgradeApp( + previous: Version, + current: Version, + appPreferences: DataStore, + ) { + if (previous.isEqualOrBefore(Version.fromString("0.1.0-2-g0"))) { + appPreferences.updateData { + it.updatePlaybackOverrides { + ac3Supported = AppPreference.Ac3Supported.defaultValue + downmixStereo = AppPreference.DownMixStereo.defaultValue + directPlayAss = AppPreference.DirectPlayAss.defaultValue + directPlayPgs = AppPreference.DirectPlayPgs.defaultValue + } } } - } - } - - if (previous.isEqualOrBefore(Version.fromString("0.3.4-2-g0"))) { - appPreferences.updateData { - it.updateMpvOptions { - useGpuNext = AppPreference.MpvGpuNext.defaultValue - } - } - } - - if (previous.isEqualOrBefore(Version.fromString("0.3.4-4-g0"))) { - appPreferences.updateData { - it.update { - signInAutomatically = AppPreference.SignInAuto.defaultValue - } - } - } - - if (previous.isEqualOrBefore(Version.fromString("0.3.5-0-g0"))) { - appPreferences.updateData { - it.updateSubtitlePreferences { - if (edgeThickness < 1) { - edgeThickness = SubtitleSettings.EdgeThickness.defaultValue.toInt() + if (previous.isEqualOrBefore(Version.fromString("0.2.3-6-g0"))) { + appPreferences.updateData { + it.updateInterfacePreferences { + navDrawerSwitchOnFocus = AppPreference.NavDrawerSwitchOnFocus.defaultValue + } } } - } - } - if (previous.isEqualOrBefore(Version.fromString("0.3.5-56-g0"))) { - appPreferences.updateData { - it.updateLiveTvPreferences { - showHeader = AppPreference.LiveTvShowHeader.defaultValue - favoriteChannelsAtBeginning = - AppPreference.LiveTvFavoriteChannelsBeginning.defaultValue - sortByRecentlyWatched = - AppPreference.LiveTvChannelSortByWatched.defaultValue - colorCodePrograms = - AppPreference.LiveTvColorCodePrograms.defaultValue - } - } - } - - if (previous.isEqualOrBefore(Version.fromString("0.3.6-52-g0"))) { - if (Build.MODEL.equals("shield android tv", ignoreCase = true)) { - appPreferences.updateData { - it.updateMpvOptions { - useGpuNext = false + if (previous.isEqualOrBefore(Version.fromString("0.2.5-11-g0"))) { + appPreferences.updateData { + it.updateInterfacePreferences { + showClock = AppPreference.ShowClock.defaultValue + } + } + } + if (previous.isEqualOrBefore(Version.fromString("0.2.7-1-g0"))) { + PreferencesViewModel.resetSubtitleSettings(appPreferences) + } + if (previous.isEqualOrBefore(Version.fromString("0.3.2-4-g0"))) { + appPreferences.updateData { + it.updateSubtitlePreferences { + margin = SubtitleSettings.Margin.defaultValue.toInt() + } } } - } - } - // TODO temporarily disabled until some MPV bugs are fixed + if (previous.isEqualOrBefore(Version.fromString("0.3.4"))) { + appPreferences.updateData { + it.updateAdvancedPreferences { + if (imageDiskCacheSizeBytes < (AppPreference.ImageDiskCacheSize.min * AppPreference.MEGA_BIT)) { + imageDiskCacheSizeBytes = + AppPreference.ImageDiskCacheSize.defaultValue * AppPreference.MEGA_BIT + } + } + } + } + + if (previous.isEqualOrBefore(Version.fromString("0.3.4-2-g0"))) { + appPreferences.updateData { + it.updateMpvOptions { + useGpuNext = AppPreference.MpvGpuNext.defaultValue + } + } + } + + if (previous.isEqualOrBefore(Version.fromString("0.3.4-4-g0"))) { + appPreferences.updateData { + it.update { + signInAutomatically = AppPreference.SignInAuto.defaultValue + } + } + } + + if (previous.isEqualOrBefore(Version.fromString("0.3.5-0-g0"))) { + appPreferences.updateData { + it.updateSubtitlePreferences { + if (edgeThickness < 1) { + edgeThickness = SubtitleSettings.EdgeThickness.defaultValue.toInt() + } + } + } + } + if (previous.isEqualOrBefore(Version.fromString("0.3.5-56-g0"))) { + appPreferences.updateData { + it.updateLiveTvPreferences { + showHeader = AppPreference.LiveTvShowHeader.defaultValue + favoriteChannelsAtBeginning = + AppPreference.LiveTvFavoriteChannelsBeginning.defaultValue + sortByRecentlyWatched = + AppPreference.LiveTvChannelSortByWatched.defaultValue + colorCodePrograms = + AppPreference.LiveTvColorCodePrograms.defaultValue + } + } + } + + if (previous.isEqualOrBefore(Version.fromString("0.3.6-52-g0"))) { + if (Build.MODEL.equals("shield android tv", ignoreCase = true)) { + appPreferences.updateData { + it.updateMpvOptions { + useGpuNext = false + } + } + } + } + + // TODO temporarily disabled until some MPV bugs are fixed // if (previous.isEqualOrBefore(Version.fromString("0.4.0-1-g0"))) { // appPreferences.updateData { // it.updatePlaybackPreferences { playerBackend = PlayerBackend.PREFER_MPV } @@ -212,66 +219,77 @@ suspend fun upgradeApp( // showToast(context, context.getString(R.string.upgrade_mpv_toast), Toast.LENGTH_LONG) // } - if (previous.isEqualOrBefore(Version.fromString("0.4.0-2-g0"))) { - appPreferences.updateData { - it.updateMpvOptions { - useGpuNext = false + if (previous.isEqualOrBefore(Version.fromString("0.4.0-2-g0"))) { + appPreferences.updateData { + it.updateMpvOptions { + useGpuNext = false + } + } } - } - } - if (previous.isEqualOrBefore(Version.fromString("0.4.1-6-g0"))) { - appPreferences.updateData { - it.updateInterfacePreferences { - subtitlesPreferences = - subtitlesPreferences - .toBuilder() - .apply { - imageSubtitleOpacity = SubtitleSettings.ImageOpacity.defaultValue.toInt() - }.build() - // Copy current subtitle prefs as HDR ones - hdrSubtitlesPreferences = subtitlesPreferences.toBuilder().build() + if (previous.isEqualOrBefore(Version.fromString("0.4.1-6-g0"))) { + appPreferences.updateData { + it.updateInterfacePreferences { + subtitlesPreferences = + subtitlesPreferences + .toBuilder() + .apply { + imageSubtitleOpacity = + SubtitleSettings.ImageOpacity.defaultValue.toInt() + }.build() + // Copy current subtitle prefs as HDR ones + hdrSubtitlesPreferences = subtitlesPreferences.toBuilder().build() + } + } } - } - } - if (previous.isEqualOrBefore(Version.fromString("0.4.1-7-g0"))) { - appPreferences.updateData { - it.updatePhotoPreferences { - slideshowDuration = AppPreference.SlideshowDuration.defaultValue + if (previous.isEqualOrBefore(Version.fromString("0.4.1-7-g0"))) { + appPreferences.updateData { + it.updatePhotoPreferences { + slideshowDuration = AppPreference.SlideshowDuration.defaultValue + } + } } - } - } - if (previous.isEqualOrBefore(Version.fromString("0.4.1-14-g0"))) { - appPreferences.updateData { - it.updateHomePagePreferences { - maxDaysNextUp = AppPreference.MaxDaysNextUp.defaultValue.toInt() + if (previous.isEqualOrBefore(Version.fromString("0.4.1-14-g0"))) { + appPreferences.updateData { + it.updateHomePagePreferences { + maxDaysNextUp = AppPreference.MaxDaysNextUp.defaultValue.toInt() + } + } } - } - } - if (previous.isEqualOrBefore(Version.fromString("0.5.3-0-g0"))) { - appPreferences.updateData { - it.updateScreensaverPreferences { - startDelay = ScreensaverPreference.DEFAULT_START_DELAY - duration = ScreensaverPreference.DEFAULT_DURATION - animate = ScreensaverPreference.Animate.defaultValue - maxAgeFilter = ScreensaverPreference.DEFAULT_MAX_AGE - clearItemTypes() - addItemTypes(BaseItemKind.MOVIE.serialName) - addItemTypes(BaseItemKind.SERIES.serialName) + if (previous.isEqualOrBefore(Version.fromString("0.5.3-0-g0"))) { + appPreferences.updateData { + it.updateScreensaverPreferences { + startDelay = ScreensaverPreference.DEFAULT_START_DELAY + duration = ScreensaverPreference.DEFAULT_DURATION + animate = ScreensaverPreference.Animate.defaultValue + maxAgeFilter = ScreensaverPreference.DEFAULT_MAX_AGE + clearItemTypes() + addItemTypes(BaseItemKind.MOVIE.serialName) + addItemTypes(BaseItemKind.SERIES.serialName) + } + } } - } - } - if (previous.isEqualOrBefore(Version.fromString("0.5.3-1-g0"))) { - appPreferences.updateData { - it.updateMusicPreferences { - showBackdrop = true - showLyrics = true - showAlbumArt = true + if (previous.isEqualOrBefore(Version.fromString("0.5.4-0-g0"))) { + seerrServerDao.getServers().forEach { + val server = it.server + seerrServerDao.updateServer( + server.copy(url = migrateSeerrUrl(server.url)), + ) + } + } + + if (previous.isEqualOrBefore(Version.fromString("0.6.0-0-g0"))) { + appPreferences.updateData { + it.updateMusicPreferences { + showBackdrop = true + showLyrics = true + showAlbumArt = true + } + } } } } -} diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrApi.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrApi.kt index c68cd3d2..5c62655f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrApi.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrApi.kt @@ -2,6 +2,7 @@ package com.github.damontecres.wholphin.services import com.github.damontecres.wholphin.api.seerr.SeerrApiClient import com.github.damontecres.wholphin.ui.isNotNullOrBlank +import com.github.damontecres.wholphin.ui.setup.seerr.createSeerrApiUrl import okhttp3.OkHttpClient /** @@ -24,6 +25,6 @@ class SeerrApi( baseUrl: String, apiKey: String?, ) { - api = SeerrApiClient(baseUrl, apiKey, okHttpClient) + api = SeerrApiClient(createSeerrApiUrl(baseUrl), apiKey, okHttpClient) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt index 80eafb7a..0f073af6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt @@ -19,6 +19,7 @@ import com.github.damontecres.wholphin.data.model.SeerrUser import com.github.damontecres.wholphin.data.model.hasPermission import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient import com.github.damontecres.wholphin.ui.launchIO +import com.github.damontecres.wholphin.ui.setup.seerr.createSeerrApiUrl import com.github.damontecres.wholphin.util.LoadingState import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.scopes.ActivityScoped @@ -30,6 +31,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.supervisorScope import okhttp3.OkHttpClient +import org.jellyfin.sdk.model.api.ImageType import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -163,7 +165,7 @@ class SeerrServerRepository val apiKey = passwordOrApiKey.takeIf { authMethod == SeerrAuthMethod.API_KEY } val api = SeerrApiClient( - url, + createSeerrApiUrl(url), apiKey, okHttpClient .newBuilder() @@ -331,3 +333,24 @@ class UserSwitchListener } } } + +fun CurrentSeerr?.imageUrlBuilder( + imageType: ImageType, + path: String?, +): String? { + if (this == null) return null + val cacheImages = serverConfig.cacheImages == true + val base = + if (cacheImages) { + server.url.removeSuffix("/") + "/imageproxy/tmdb" + } else { + "https://image.tmdb.org" + } + val prefix = + when (imageType) { + ImageType.PRIMARY -> "/t/p/w500" + ImageType.BACKDROP -> "/t/p/w1920_and_h1080_multi_faces" + else -> throw IllegalArgumentException("Image type not supported: $imageType") + } + return "${base}${prefix}$path" +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrService.kt index 14aa0b9a..0c1801ac 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrService.kt @@ -1,12 +1,25 @@ package com.github.damontecres.wholphin.services import com.github.damontecres.wholphin.api.seerr.SeerrApiClient +import com.github.damontecres.wholphin.api.seerr.model.CreditCast +import com.github.damontecres.wholphin.api.seerr.model.CreditCrew +import com.github.damontecres.wholphin.api.seerr.model.MediaInfo +import com.github.damontecres.wholphin.api.seerr.model.MovieDetails +import com.github.damontecres.wholphin.api.seerr.model.MovieResult import com.github.damontecres.wholphin.api.seerr.model.SearchGet200ResponseResultsInner import com.github.damontecres.wholphin.api.seerr.model.TvDetails +import com.github.damontecres.wholphin.api.seerr.model.TvResult import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.DiscoverItem +import com.github.damontecres.wholphin.data.model.SeerrAvailability +import com.github.damontecres.wholphin.data.model.SeerrItemType +import com.github.damontecres.wholphin.ui.isNotNullOrBlank +import com.github.damontecres.wholphin.ui.toLocalDate import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.ImageType +import org.jellyfin.sdk.model.serializer.toUUIDOrNull import javax.inject.Inject import javax.inject.Singleton @@ -23,6 +36,7 @@ class SeerrService constructor( private val seerApi: SeerrApi, private val seerrServerRepository: SeerrServerRepository, + private val imageUrlService: ImageUrlService, ) { val api: SeerrApiClient get() = seerApi.api @@ -41,35 +55,35 @@ class SeerrService api.searchApi .discoverTvGet(page = page) .results - ?.map(::DiscoverItem) + ?.map { createDiscoverItem(it) } .orEmpty() suspend fun discoverMovies(page: Int = 1): List = api.searchApi .discoverMoviesGet(page = page) .results - ?.map(::DiscoverItem) + ?.map { createDiscoverItem(it) } .orEmpty() suspend fun trending(page: Int = 1): List = api.searchApi .discoverTrendingGet(page = page) .results - ?.map(::DiscoverItem) + ?.map { createDiscoverItem(it) } .orEmpty() suspend fun upcomingMovies(page: Int = 1): List = api.searchApi .discoverMoviesUpcomingGet(page = page) .results - ?.map(::DiscoverItem) + ?.map { createDiscoverItem(it) } .orEmpty() suspend fun upcomingTv(page: Int = 1): List = api.searchApi .discoverTvUpcomingGet(page = page) .results - ?.map(::DiscoverItem) + ?.map { createDiscoverItem(it) } .orEmpty() /** @@ -90,14 +104,14 @@ class SeerrService api.moviesApi .movieMovieIdSimilarGet(movieId = it) .results - ?.map(::DiscoverItem) + ?.map { createDiscoverItem(it) } } BaseItemKind.SERIES, BaseItemKind.SEASON, BaseItemKind.EPISODE -> { api.tvApi .tvTvIdSimilarGet(tvId = it) .results - ?.map(::DiscoverItem) + ?.map { createDiscoverItem(it) } } BaseItemKind.PERSON -> { @@ -107,12 +121,12 @@ class SeerrService val cast = credits.cast ?.take(25) - ?.map(::DiscoverItem) + ?.map { createDiscoverItem(it) } .orEmpty() val crew = credits.crew ?.take(25) - ?.map(::DiscoverItem) + ?.map { createDiscoverItem(it) } .orEmpty() cast + crew } @@ -138,4 +152,174 @@ class SeerrService } else { null } + + private suspend fun createImageUrl( + imageType: ImageType, + path: String?, + mediaInfo: MediaInfo?, + ): String? { + if (mediaInfo != null) { + val itemId = + if (mediaInfo.jellyfinMediaId.isNotNullOrBlank()) { + mediaInfo.jellyfinMediaId.toUUIDOrNull() + } else if (mediaInfo.jellyfinMediaId4k.isNotNullOrBlank()) { + mediaInfo.jellyfinMediaId4k.toUUIDOrNull() + } else { + null + } + if (itemId != null) { + return imageUrlService.getItemImageUrl( + itemId = itemId, + imageType = imageType, + ) + } + } + val current = seerrServerRepository.current.firstOrNull() ?: return null + val cacheImages = current.serverConfig.cacheImages == true + val base = + if (cacheImages) { + current.server.url.removeSuffix("/") + "/imageproxy/tmdb" + } else { + "https://image.tmdb.org" + } + val prefix = + when (imageType) { + ImageType.PRIMARY -> "/t/p/w500" + ImageType.BACKDROP -> "/t/p/w1920_and_h1080_multi_faces" + else -> throw IllegalArgumentException("Image type not supported: $imageType") + } + return "${base}${prefix}$path" + } + + suspend fun createDiscoverItem(movie: MovieResult): DiscoverItem = + DiscoverItem( + id = movie.id, + type = SeerrItemType.MOVIE, + title = movie.title, + subtitle = null, + overview = movie.overview, + availability = + SeerrAvailability.from(movie.mediaInfo?.status) + ?: SeerrAvailability.UNKNOWN, + releaseDate = toLocalDate(movie.releaseDate), + posterUrl = createImageUrl(ImageType.PRIMARY, movie.posterPath, movie.mediaInfo), + backDropUrl = createImageUrl(ImageType.BACKDROP, movie.backdropPath, movie.mediaInfo), + jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), + ) + + suspend fun createDiscoverItem(movie: MovieDetails): DiscoverItem = + DiscoverItem( + id = movie.id ?: -1, + type = SeerrItemType.MOVIE, + title = movie.title, + subtitle = null, + overview = movie.overview, + availability = + SeerrAvailability.from(movie.mediaInfo?.status) + ?: SeerrAvailability.UNKNOWN, + releaseDate = toLocalDate(movie.releaseDate), + posterUrl = createImageUrl(ImageType.PRIMARY, movie.posterPath, movie.mediaInfo), + backDropUrl = createImageUrl(ImageType.BACKDROP, movie.backdropPath, movie.mediaInfo), + jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), + ) + + suspend fun createDiscoverItem(tv: TvResult): DiscoverItem = + DiscoverItem( + id = tv.id!!, + type = SeerrItemType.TV, + title = tv.name, + subtitle = null, + overview = tv.overview, + availability = + SeerrAvailability.from(tv.mediaInfo?.status) + ?: SeerrAvailability.UNKNOWN, + releaseDate = toLocalDate(tv.firstAirDate), + posterUrl = createImageUrl(ImageType.PRIMARY, tv.posterPath, tv.mediaInfo), + backDropUrl = createImageUrl(ImageType.BACKDROP, tv.backdropPath, tv.mediaInfo), + jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), + ) + + suspend fun createDiscoverItem(tv: TvDetails): DiscoverItem = + DiscoverItem( + id = tv.id!!, + type = SeerrItemType.TV, + title = tv.name, + subtitle = null, + overview = tv.overview, + availability = + SeerrAvailability.from(tv.mediaInfo?.status) + ?: SeerrAvailability.UNKNOWN, + releaseDate = toLocalDate(tv.firstAirDate), + posterUrl = createImageUrl(ImageType.PRIMARY, tv.posterPath, tv.mediaInfo), + backDropUrl = createImageUrl(ImageType.BACKDROP, tv.backdropPath, tv.mediaInfo), + jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), + ) + + suspend fun createDiscoverItem(search: SeerrSearchResult): DiscoverItem = + DiscoverItem( + id = search.id, + type = SeerrItemType.fromString(search.mediaType), + title = search.title ?: search.name, + subtitle = null, + overview = search.overview, + availability = + SeerrAvailability.from(search.mediaInfo?.status) + ?: SeerrAvailability.UNKNOWN, + releaseDate = toLocalDate(search.releaseDate ?: search.firstAirDate), + posterUrl = createImageUrl(ImageType.PRIMARY, search.posterPath, search.mediaInfo), + backDropUrl = createImageUrl(ImageType.BACKDROP, search.backdropPath, search.mediaInfo), + jellyfinItemId = search.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), + ) + + suspend fun createDiscoverItem(credit: CreditCast): DiscoverItem = + DiscoverItem( + id = credit.id!!, + type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON), + title = credit.name ?: credit.title, + subtitle = credit.character, + overview = credit.overview, + availability = + SeerrAvailability.from(credit.mediaInfo?.status) + ?: SeerrAvailability.UNKNOWN, + releaseDate = toLocalDate(credit.firstAirDate), + posterUrl = + createImageUrl( + ImageType.PRIMARY, + credit.posterPath ?: credit.profilePath, + credit.mediaInfo, + ), + backDropUrl = + createImageUrl( + ImageType.BACKDROP, + credit.backdropPath, + credit.mediaInfo, + ), + jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), + ) + + suspend fun createDiscoverItem(credit: CreditCrew): DiscoverItem = + DiscoverItem( + id = credit.id!!, + type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON), + title = credit.name ?: credit.title, + subtitle = credit.job, + overview = credit.overview, + availability = + SeerrAvailability.from(credit.mediaInfo?.status) + ?: SeerrAvailability.UNKNOWN, + releaseDate = toLocalDate(credit.firstAirDate), + posterUrl = + createImageUrl( + ImageType.PRIMARY, + credit.posterPath ?: credit.profilePath, + credit.mediaInfo, + ), + backDropUrl = + createImageUrl( + ImageType.BACKDROP, + credit.backdropPath, + credit.mediaInfo, + ), + jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), + ) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt b/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt index f4aa80dc..709549cf 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/UpdateChecker.kt @@ -14,7 +14,9 @@ import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.content.edit import androidx.preference.PreferenceManager +import com.github.damontecres.wholphin.BuildConfig import com.github.damontecres.wholphin.R +import com.github.damontecres.wholphin.services.UpdateChecker.Companion.ASSET_NAME import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.showToast @@ -51,9 +53,8 @@ class UpdateChecker @param:StandardOkHttpClient private val okHttpClient: OkHttpClient, ) { companion object { - // TODO apk names - private const val ASSET_NAME = "Wholphin" - private const val APK_NAME = "$ASSET_NAME.apk" + const val ASSET_NAME = "Wholphin" + const val APK_NAME = "$ASSET_NAME.apk" private const val APK_MIME_TYPE = "application/vnd.android.package-archive" @@ -118,11 +119,6 @@ class UpdateChecker suspend fun getLatestRelease(updateUrl: String): Release? { return withContext(Dispatchers.IO) { - val preferRelease = - PreferenceManager - .getDefaultSharedPreferences(context) - .getBoolean("updatePreferRelease", true) - val request = Request .Builder() @@ -140,7 +136,7 @@ class UpdateChecker val downloadUrl = result.jsonObject["assets"] ?.jsonArray - ?.let { assets -> getDownloadUrl(assets, preferRelease) } + ?.let { assets -> getDownloadUrl(assets, BuildConfig.DEBUG) } Timber.v("version=$version, downloadUrl=$downloadUrl") if (version != null) { val notes = @@ -165,35 +161,6 @@ class UpdateChecker } } - private fun getDownloadUrl( - assets: JsonArray, - preferRelease: Boolean, - ): String? { - val abiSuffix = Build.SUPPORTED_ABIS.firstOrNull().let { if (it != null) "-$it" else "" } - val releaseSuffix = if (preferRelease) "-release" else "-debug" - val preferredNames = - listOf( - "$ASSET_NAME${releaseSuffix}$abiSuffix.apk", - "$ASSET_NAME$releaseSuffix.apk", - "$ASSET_NAME.apk", - ) - var preferredAsset: JsonObject? = null - outer@ for (name in preferredNames) { - for (asset in assets) { - val assetName = - asset.jsonObject["name"]?.jsonPrimitive?.contentOrNull - if (name == assetName) { - preferredAsset = asset.jsonObject - break@outer - } - } - } - return preferredAsset - ?.get("browser_download_url") - ?.jsonPrimitive - ?.contentOrNull - } - suspend fun installRelease( release: Release, callback: DownloadCallback, @@ -387,3 +354,33 @@ suspend fun copyTo( } return bytesCopied } + +fun getDownloadUrl( + assets: JsonArray, + debug: Boolean, + supportedAbis: List = Build.SUPPORTED_ABIS.toList(), +): String? { + val abiSuffix = supportedAbis.firstOrNull().let { if (it != null) "-$it" else "" } + val releaseSuffix = if (debug) "-debug" else "-release" + val preferredNames = + buildList { + add("$ASSET_NAME${releaseSuffix}$abiSuffix.apk") + add("$ASSET_NAME$releaseSuffix.apk") + if (!debug) add("$ASSET_NAME.apk") + } + var preferredAsset: JsonObject? = null + outer@ for (name in preferredNames) { + for (asset in assets) { + val assetName = + asset.jsonObject["name"]?.jsonPrimitive?.contentOrNull + if (name == assetName) { + preferredAsset = asset.jsonObject + break@outer + } + } + } + return preferredAsset + ?.get("browser_download_url") + ?.jsonPrimitive + ?.contentOrNull +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt index a50e848d..3edd7dd6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt @@ -103,7 +103,7 @@ class DiscoverMovieViewModel ) { Timber.v("Init for movie %s", item.id) val movie = fetchAndSetItem().await() - val discoveredItem = DiscoverItem(movie) + val discoveredItem = seerrService.createDiscoverItem(movie) backdropService.submit(discoveredItem) updateCanCancel() @@ -121,7 +121,7 @@ class DiscoverMovieViewModel seerrService.api.moviesApi .movieMovieIdSimilarGet(movieId = item.id, page = 1) .results - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() similar.setValueOnMain(result) } @@ -130,7 +130,7 @@ class DiscoverMovieViewModel seerrService.api.moviesApi .movieMovieIdRecommendationsGet(movieId = item.id, page = 1) .results - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() recommended.setValueOnMain(result) } @@ -138,11 +138,11 @@ class DiscoverMovieViewModel val people = movie.credits ?.cast - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() + movie.credits ?.crew - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() this@DiscoverMovieViewModel.people.setValueOnMain(people) val trailers = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverPersonPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverPersonPage.kt index 32d38aa5..f7d50f31 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverPersonPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverPersonPage.kt @@ -67,11 +67,11 @@ class DiscoverPersonViewModel .let { credits -> val cast = credits.cast - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() val crew = credits.crew - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() cast + crew } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt index da6550ec..54b3d98f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt @@ -102,7 +102,7 @@ class DiscoverSeriesViewModel ) { Timber.v("Init for tv %s", item.id) val tv = fetchAndSetItem().await() - val discoveredItem = DiscoverItem(tv) + val discoveredItem = seerrService.createDiscoverItem(tv) backdropService.submit(discoveredItem) updateSeasonStatus() @@ -121,7 +121,7 @@ class DiscoverSeriesViewModel seerrService.api.tvApi .tvTvIdSimilarGet(tvId = item.id, page = 1) .results - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() similar.setValueOnMain(result) } @@ -130,7 +130,7 @@ class DiscoverSeriesViewModel seerrService.api.tvApi .tvTvIdRecommendationsGet(tvId = item.id, page = 1) .results - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() recommended.setValueOnMain(result) } @@ -138,11 +138,11 @@ class DiscoverSeriesViewModel val people = tv.credits ?.cast - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() + tv.credits ?.crew - ?.map(::DiscoverItem) + ?.map { seerrService.createDiscoverItem(it) } .orEmpty() this@DiscoverSeriesViewModel.people.setValueOnMain(people) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt index 3ed615e9..68cfa308 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt @@ -125,20 +125,6 @@ fun SeriesOverview( var rowFocused by rememberInt() var showDeleteDialog by remember { mutableStateOf(null) } - LaunchedEffect(episodes) { - episodes?.let { episodes -> - if (episodes is EpisodeList.Success) { - if (episodes.episodes.isNotEmpty()) { - // TODO focus on first episode when changing seasons? -// firstItemFocusRequester.requestFocus() - episodes.episodes.getOrNull(position.episodeRowIndex)?.let { - viewModel.refreshEpisode(it.id, position.episodeRowIndex) - } - } - } - } - } - LaunchedEffect(position, episodes) { val focusedEpisode = (episodes as? EpisodeList.Success) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt index ee07a170..2d044670 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt @@ -239,7 +239,9 @@ class SeriesViewModel val tv = if (active) { try { - seerrService.getTvSeries(item)?.let { DiscoverItem(it) } + seerrService + .getTvSeries(item) + ?.let { seerrService.createDiscoverItem(it) } } catch (ex: Exception) { Timber.e(ex) null @@ -387,12 +389,6 @@ class SeriesViewModel withContext(Dispatchers.Main) { this@SeriesViewModel.episodes.value = episodes } - if (currentEpisodes == null || currentEpisodes.seasonId != seasonId) { - (episodes as? EpisodeList.Success) - ?.let { - it.episodes.getOrNull(it.initialEpisodeIndex) - }?.let { lookupPeopleInEpisode(it) } - } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrRequestsPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrRequestsPage.kt index 85863650..6ed79a06 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrRequestsPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrRequestsPage.kt @@ -85,13 +85,13 @@ class SeerrRequestsViewModel seerrService.api.moviesApi .movieMovieIdGet( movieId = request.media.tmdbId, - ).let { DiscoverItem(it) } + ).let { seerrService.createDiscoverItem(it) } } SeerrItemType.TV -> { seerrService.api.tvApi .tvTvIdGet(tvId = request.media.tmdbId) - .let { DiscoverItem(it) } + .let { seerrService.createDiscoverItem(it) } } SeerrItemType.PERSON -> { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/SearchPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/SearchPage.kt index 97f86f23..fde8e3aa 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/SearchPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/SearchPage.kt @@ -178,7 +178,7 @@ class SearchViewModel val results = seerrService .search(query) - .map { DiscoverItem(it) } + .map { seerrService.createDiscoverItem(it) } .filter { it.type == SeerrItemType.MOVIE || it.type == SeerrItemType.TV } seerrResults.setValueOnMain(SearchResult.SuccessSeerr(results)) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt index 02e97ff5..7c05194a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt @@ -127,37 +127,50 @@ class SwitchSeerrViewModel } fun createUrls(url: String): List { - val urls = mutableListOf() + val urls = mutableListOf() if (url.startsWith("http://") || url.startsWith("https://")) { - urls.add(url) val httpUrl = url.toHttpUrl() + urls.add(httpUrl) if (HttpUrl.defaultPort(httpUrl.scheme) == httpUrl.port) { - urls.add("$url:5055") + urls.add(httpUrl.newBuilder().port(5055).build()) } } else { - urls.add("http://$url") val httpUrl = "http://$url".toHttpUrl() + urls.add(httpUrl) if (httpUrl.port == 80) { - urls.add("https://$url") - urls.add("http://$url:5055") - urls.add("https://$url:5055") + urls.add(httpUrl.newBuilder().scheme("https").build()) + urls.add(httpUrl.newBuilder().port(5055).build()) + urls.add( + httpUrl + .newBuilder() + .scheme("https") + .port(5055) + .build(), + ) } else { - urls.add("https://$url") + urls.add(httpUrl.newBuilder().scheme("https").build()) } } - return urls.map { cleanUrl(it).toHttpUrl() } + return urls } -private fun cleanUrl(url: String) = - if (!url.endsWith("/api/v1")) { +fun createSeerrApiUrl(url: String): String = + if (url.isBlank()) { + url + } else if (url.endsWith("/api/v1") || url.endsWith("/api/v1/")) { + url + } else { url .toHttpUrl() .newBuilder() - .apply { - addPathSegment("api") - addPathSegment("v1") - }.build() + .addPathSegment("api") + .addPathSegment("v1") + .build() .toString() - } else { - url } + +fun migrateSeerrUrl(url: String): String { + var url = url.removeSuffix("/api/v1/").removeSuffix("/api/v1") + if (!url.endsWith("/")) url += "/" + return url +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowPage.kt index edb1e859..2b3ef667 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowPage.kt @@ -395,8 +395,9 @@ fun SlideshowPage( ImageRequest .Builder(LocalContext.current) .data(imageState.url) - .size(Size.ORIGINAL) - .transitionFactory(CrossFadeFactory(750.milliseconds)) + .apply { + if (isZoomed) size(Size.ORIGINAL) + }.transitionFactory(CrossFadeFactory(750.milliseconds)) .useExistingImageAsPlaceholder(true) .build(), contentDescription = null, diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/ApiRequestPager.kt b/app/src/main/java/com/github/damontecres/wholphin/util/ApiRequestPager.kt index 4b30c418..08f9318e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/ApiRequestPager.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/ApiRequestPager.kt @@ -146,17 +146,17 @@ class ApiRequestPager( position: Int, itemId: UUID, ) { - val item = - api.userLibraryApi.getItem(itemId).content.let { - BaseItem.from( - it, - api, - useSeriesForPrimary, - ) - } - val pageNumber = position / pageSize - val index = position - pageNumber * pageSize mutex.withLock { + val item = + api.userLibraryApi.getItem(itemId).content.let { + BaseItem.from( + it, + api, + useSeriesForPrimary, + ) + } + val pageNumber = position / pageSize + val index = position - pageNumber * pageSize val page = cachedPages.getIfPresent(pageNumber) if (page != null && index in page.indices) { page[index] = item diff --git a/app/src/main/seerr/seerr-api.yml b/app/src/main/seerr/seerr-api.yml index d6b719ec..4c21b699 100644 --- a/app/src/main/seerr/seerr-api.yml +++ b/app/src/main/seerr/seerr-api.yml @@ -711,6 +711,8 @@ components: type: boolean series4kEnabled: type: boolean + cacheImages: + type: boolean MovieResult: type: object required: diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/TestSeerr.kt b/app/src/test/java/com/github/damontecres/wholphin/test/TestSeerr.kt index a4acf93f..c19ffb81 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/test/TestSeerr.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/test/TestSeerr.kt @@ -1,7 +1,9 @@ package com.github.damontecres.wholphin.test +import com.github.damontecres.wholphin.ui.setup.seerr.createSeerrApiUrl import com.github.damontecres.wholphin.ui.setup.seerr.createUrls -import org.junit.Assert +import com.github.damontecres.wholphin.ui.setup.seerr.migrateSeerrUrl +import org.junit.Assert.assertEquals import org.junit.Test class TestSeerr { @@ -13,12 +15,12 @@ class TestSeerr { val expected = listOf( - "http://jellyseerr.com/api/v1", - "https://jellyseerr.com/api/v1", - "http://jellyseerr.com:5055/api/v1", - "https://jellyseerr.com:5055/api/v1", + "http://jellyseerr.com/", + "https://jellyseerr.com/", + "http://jellyseerr.com:5055/", + "https://jellyseerr.com:5055/", ) - Assert.assertEquals(expected, urls) + assertEquals(expected, urls) } @Test @@ -29,10 +31,10 @@ class TestSeerr { val expected = listOf( - "https://jellyseerr.com/api/v1", - "https://jellyseerr.com:5055/api/v1", + "https://jellyseerr.com/", + "https://jellyseerr.com:5055/", ) - Assert.assertEquals(expected, urls) + assertEquals(expected, urls) } @Test @@ -43,10 +45,10 @@ class TestSeerr { val expected = listOf( - "http://jellyseerr.com/api/v1", - "http://jellyseerr.com:5055/api/v1", + "http://jellyseerr.com/", + "http://jellyseerr.com:5055/", ) - Assert.assertEquals(expected, urls) + assertEquals(expected, urls) } @Test @@ -57,10 +59,10 @@ class TestSeerr { val expected = listOf( - "http://jellyseerr.com:5055/api/v1", - "https://jellyseerr.com:5055/api/v1", + "http://jellyseerr.com:5055/", + "https://jellyseerr.com:5055/", ) - Assert.assertEquals(expected, urls) + assertEquals(expected, urls) } @Test @@ -71,10 +73,10 @@ class TestSeerr { val expected = listOf( - "http://10.0.0.2:443/api/v1", - "https://10.0.0.2/api/v1", + "http://10.0.0.2:443/", + "https://10.0.0.2/", ) - Assert.assertEquals(expected, urls) + assertEquals(expected, urls) } @Test @@ -85,9 +87,87 @@ class TestSeerr { val expected = listOf( - "http://10.0.0.2:8080/api/v1", - "https://10.0.0.2:8080/api/v1", + "http://10.0.0.2:8080/", + "https://10.0.0.2:8080/", ) - Assert.assertEquals(expected, urls) + assertEquals(expected, urls) + } + + @Test + fun testCreateUrls7() { + val urls = + createUrls("http://10.0.0.2:80") + .map { it.toString() } + + val expected = + listOf( + "http://10.0.0.2/", + "http://10.0.0.2:5055/", + ) + assertEquals(expected, urls) + } + + @Test + fun testCreateUrls8() { + val urls = + createUrls("https://10.0.0.2:443") + .map { it.toString() } + + val expected = + listOf( + "https://10.0.0.2/", + "https://10.0.0.2:5055/", + ) + assertEquals(expected, urls) + } + + @Test + fun `Test createUrls for path`() { + val urls = + createUrls("https://jellyseerr.com/seerr/") + .map { it.toString() } + + val expected = + listOf( + "https://jellyseerr.com/seerr/", + "https://jellyseerr.com:5055/seerr/", + ) + assertEquals(expected, urls) + } + + @Test + fun `Test build api url`() { + var url = "https://jellyseerr.com/" + assertEquals("https://jellyseerr.com/api/v1", createSeerrApiUrl(url)) + + url = "https://jellyseerr.com/path" + assertEquals("https://jellyseerr.com/path/api/v1", createSeerrApiUrl(url)) + + url = "http://jellyseerr.com:5055/" + assertEquals("http://jellyseerr.com:5055/api/v1", createSeerrApiUrl(url)) + + url = "http://jellyseerr.com:7878/path/" + assertEquals("http://jellyseerr.com:7878/path/api/v1", createSeerrApiUrl(url)) + + url = "http://jellyseerr.com/api/v1" + assertEquals("http://jellyseerr.com/api/v1", createSeerrApiUrl(url)) + + url = "http://jellyseerr.com/api/v1/" + assertEquals("http://jellyseerr.com/api/v1/", createSeerrApiUrl(url)) + + url = "http://jellyseerr.com/path/api/v1" + assertEquals("http://jellyseerr.com/path/api/v1", createSeerrApiUrl(url)) + } + + @Test + fun `Test migration`() { + assertEquals("http://10.0.0.2/", migrateSeerrUrl("http://10.0.0.2/api/v1")) + assertEquals("https://10.0.0.2/", migrateSeerrUrl("https://10.0.0.2/api/v1")) + assertEquals("http://10.0.0.2:5055/", migrateSeerrUrl("http://10.0.0.2:5055/api/v1")) + assertEquals("http://10.0.0.2/", migrateSeerrUrl("http://10.0.0.2/api/v1/")) + assertEquals("http://10.0.0.2/path/", migrateSeerrUrl("http://10.0.0.2/path/api/v1")) + assertEquals("http://10.0.0.2/api/v1/", migrateSeerrUrl("http://10.0.0.2/api/v1/api/v1")) + assertEquals("http://10.0.0.2/", migrateSeerrUrl("http://10.0.0.2/")) + assertEquals("http://10.0.0.2/", migrateSeerrUrl("http://10.0.0.2")) } } diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/TestUpdateChecker.kt b/app/src/test/java/com/github/damontecres/wholphin/test/TestUpdateChecker.kt new file mode 100644 index 00000000..ca8ef324 --- /dev/null +++ b/app/src/test/java/com/github/damontecres/wholphin/test/TestUpdateChecker.kt @@ -0,0 +1,56 @@ +package com.github.damontecres.wholphin.test + +import com.github.damontecres.wholphin.services.getDownloadUrl +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import java.nio.file.Paths +import kotlin.io.path.readText + +class TestUpdateChecker { + lateinit var releaseJson: JsonObject + val assetsJson: JsonArray by lazy { releaseJson["assets"]!!.jsonArray } + + @Before + fun setup() { + val resource = javaClass.classLoader?.getResource("release_develop.json") + Assert.assertNotNull(resource) + val fileContents = Paths.get(resource!!.toURI()).readText() + releaseJson = Json.parseToJsonElement(fileContents).jsonObject + } + + @Test + fun `Release chooses release`() { + val url = getDownloadUrl(assetsJson, false, listOf()) + Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release.apk", url) + } + + @Test + fun `Choose abi`() { + val url = getDownloadUrl(assetsJson, false, listOf("arm64-v8a")) + Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-arm64-v8a.apk", url) + } + + @Test + fun `Choose unknown abi`() { + val url = getDownloadUrl(assetsJson, false, listOf("unknown")) + Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release.apk", url) + } + + @Test + fun `Debug chooses debug`() { + val url = getDownloadUrl(assetsJson, true, listOf()) + Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug.apk", url) + } + + @Test + fun `Choose debug abi`() { + val url = getDownloadUrl(assetsJson, true, listOf("arm64-v8a")) + Assert.assertEquals("https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-arm64-v8a.apk", url) + } +} diff --git a/app/src/test/resources/release_develop.json b/app/src/test/resources/release_develop.json new file mode 100644 index 00000000..df48106f --- /dev/null +++ b/app/src/test/resources/release_develop.json @@ -0,0 +1,475 @@ +{ + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/297064448", + "assets_url": "https://api.github.com/repos/damontecres/Wholphin/releases/297064448/assets", + "upload_url": "https://uploads.github.com/repos/damontecres/Wholphin/releases/297064448/assets{?name,label}", + "html_url": "https://github.com/damontecres/Wholphin/releases/tag/develop", + "id": 297064448, + "author": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "node_id": "RE_kwDOPzwRps4RtNgA", + "tag_name": "develop", + "target_commitish": "main", + "name": "v0.5.3-8-g627b89f1", + "draft": false, + "immutable": false, + "prerelease": true, + "created_at": "2026-03-14T20:47:08Z", + "updated_at": "2026-03-14T20:54:31Z", + "published_at": "2026-03-14T20:54:31Z", + "assets": [ + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937404", + "id": 373937404, + "node_id": "RA_kwDOPzwRps4WSdT8", + "name": "Wholphin-debug-0.5.3-8-g627b89f1-44-arm64-v8a.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 65936692, + "digest": "sha256:ede68f14374dea27a92063d1aab765265cf5386c93533b01e7c666c4b8a6ea99", + "download_count": 0, + "created_at": "2026-03-14T20:54:23Z", + "updated_at": "2026-03-14T20:54:26Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-0.5.3-8-g627b89f1-44-arm64-v8a.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937407", + "id": 373937407, + "node_id": "RA_kwDOPzwRps4WSdT_", + "name": "Wholphin-debug-0.5.3-8-g627b89f1-44-armeabi-v7a.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 62726455, + "digest": "sha256:c2c6d0443af1302a8aa8a70426605e6ca67345b98a74b5b1d22b85e7de9012c2", + "download_count": 0, + "created_at": "2026-03-14T20:54:24Z", + "updated_at": "2026-03-14T20:54:25Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-0.5.3-8-g627b89f1-44-armeabi-v7a.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937406", + "id": 373937406, + "node_id": "RA_kwDOPzwRps4WSdT-", + "name": "Wholphin-debug-0.5.3-8-g627b89f1-44.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 99552740, + "digest": "sha256:d0f5bbbe0c6f6bc31f681d70d60a5730dd0dbc83f28e66922fde27f4e60cda05", + "download_count": 0, + "created_at": "2026-03-14T20:54:24Z", + "updated_at": "2026-03-14T20:54:26Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-0.5.3-8-g627b89f1-44.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937405", + "id": 373937405, + "node_id": "RA_kwDOPzwRps4WSdT9", + "name": "Wholphin-debug-arm64-v8a.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 65936692, + "digest": "sha256:ede68f14374dea27a92063d1aab765265cf5386c93533b01e7c666c4b8a6ea99", + "download_count": 0, + "created_at": "2026-03-14T20:54:23Z", + "updated_at": "2026-03-14T20:54:26Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-arm64-v8a.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937408", + "id": 373937408, + "node_id": "RA_kwDOPzwRps4WSdUA", + "name": "Wholphin-debug-armeabi-v7a.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 62726455, + "digest": "sha256:c2c6d0443af1302a8aa8a70426605e6ca67345b98a74b5b1d22b85e7de9012c2", + "download_count": 0, + "created_at": "2026-03-14T20:54:24Z", + "updated_at": "2026-03-14T20:54:26Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug-armeabi-v7a.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937422", + "id": 373937422, + "node_id": "RA_kwDOPzwRps4WSdUO", + "name": "Wholphin-debug.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 99552740, + "digest": "sha256:d0f5bbbe0c6f6bc31f681d70d60a5730dd0dbc83f28e66922fde27f4e60cda05", + "download_count": 0, + "created_at": "2026-03-14T20:54:26Z", + "updated_at": "2026-03-14T20:54:29Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-debug.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937424", + "id": 373937424, + "node_id": "RA_kwDOPzwRps4WSdUQ", + "name": "Wholphin-release-0.5.3-8-g627b89f1-44-arm64-v8a.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 56651710, + "digest": "sha256:bf010482a8b8626fb93bbb3285667b22a41c3548286d3319bda92c71eedba672", + "download_count": 0, + "created_at": "2026-03-14T20:54:26Z", + "updated_at": "2026-03-14T20:54:28Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-0.5.3-8-g627b89f1-44-arm64-v8a.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937427", + "id": 373937427, + "node_id": "RA_kwDOPzwRps4WSdUT", + "name": "Wholphin-release-0.5.3-8-g627b89f1-44-armeabi-v7a.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 53441542, + "digest": "sha256:5b8aee8f79fd5ae26dbd5de4a1b4d38b90bc6b35cd2e7fa5f2683909463da390", + "download_count": 0, + "created_at": "2026-03-14T20:54:26Z", + "updated_at": "2026-03-14T20:54:28Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-0.5.3-8-g627b89f1-44-armeabi-v7a.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937431", + "id": 373937431, + "node_id": "RA_kwDOPzwRps4WSdUX", + "name": "Wholphin-release-0.5.3-8-g627b89f1-44.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 90267831, + "digest": "sha256:bc10956a072a5a07c372a67ee8732171470529b468fd7d85a277a738604c34c5", + "download_count": 0, + "created_at": "2026-03-14T20:54:27Z", + "updated_at": "2026-03-14T20:54:29Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-0.5.3-8-g627b89f1-44.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937439", + "id": 373937439, + "node_id": "RA_kwDOPzwRps4WSdUf", + "name": "Wholphin-release-arm64-v8a.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 56651710, + "digest": "sha256:bf010482a8b8626fb93bbb3285667b22a41c3548286d3319bda92c71eedba672", + "download_count": 1, + "created_at": "2026-03-14T20:54:27Z", + "updated_at": "2026-03-14T20:54:29Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-arm64-v8a.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937452", + "id": 373937452, + "node_id": "RA_kwDOPzwRps4WSdUs", + "name": "Wholphin-release-armeabi-v7a.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 53441542, + "digest": "sha256:5b8aee8f79fd5ae26dbd5de4a1b4d38b90bc6b35cd2e7fa5f2683909463da390", + "download_count": 0, + "created_at": "2026-03-14T20:54:28Z", + "updated_at": "2026-03-14T20:54:30Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release-armeabi-v7a.apk" + }, + { + "url": "https://api.github.com/repos/damontecres/Wholphin/releases/assets/373937453", + "id": 373937453, + "node_id": "RA_kwDOPzwRps4WSdUt", + "name": "Wholphin-release.apk", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + }, + "content_type": "application/vnd.android.package-archive", + "state": "uploaded", + "size": 90267831, + "digest": "sha256:bc10956a072a5a07c372a67ee8732171470529b468fd7d85a277a738604c34c5", + "download_count": 1, + "created_at": "2026-03-14T20:54:28Z", + "updated_at": "2026-03-14T20:54:31Z", + "browser_download_url": "https://github.com/damontecres/Wholphin/releases/download/develop/Wholphin-release.apk" + } + ], + "tarball_url": "https://api.github.com/repos/damontecres/Wholphin/tarball/develop", + "zipball_url": "https://api.github.com/repos/damontecres/Wholphin/zipball/develop", + "body": "### `main` development build\n\nThis pre-release tracks the development build of Wholphin from the `main` unstable branch.\n\nSee https://github.com/damontecres/Wholphin/releases/latest for the latest stable release.\n\nIf you want to update to this version in-app, change Settings->Advanced->Update URL to https://api.github.com/repos/damontecres/Wholphin/releases/tags/develop (replace `latest` with `tags/develop`).\n\n[See changes since `v0.5.3`](https://github.com/damontecres/Wholphin/compare/v0.5.3...develop)\n" +} diff --git a/scripts/mpv/get_dependencies.sh b/scripts/mpv/get_dependencies.sh index 7ca18de8..74bff6d6 100755 --- a/scripts/mpv/get_dependencies.sh +++ b/scripts/mpv/get_dependencies.sh @@ -30,7 +30,11 @@ clone "https://gitlab.freedesktop.org/freetype/freetype.git" "VER-2-14-1" freety clone "https://github.com/libass/libass" "0.17.4" libass -clone "https://github.com/haasn/libplacebo" "v7.351.0" libplacebo --recurse-submodules +rm -rf libplacebo +git clone "https://github.com/haasn/libplacebo" --single-branch -b "master" --recurse-submodules libplacebo +pushd libplacebo || exit +git checkout 1bd8d2d6a715bc870bdffa6759e62c419000dd51 +popd || exit clone "https://github.com/mpv-player/mpv" "v0.41.0" mpv