Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import java.util.Properties

val appVersion = "1.0.0"
val appVersion = "1.0.1"

// Release signing: `keystore.properties` (not committed) points at the keystore; without it the release build is left unsigned.
val keystoreProps = Properties().apply {
Expand All @@ -23,7 +23,7 @@ android {
applicationId = "com.brebond.tipsarr"
minSdk = 26
targetSdk = 36
versionCode = 10
versionCode = 11
versionName = appVersion
buildConfigField("String", "APP_VERSION", "\"$appVersion\"")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ data class MediaDetail(
val monitored: Boolean = false,
val seasons: List<SeasonInfo>? = null,
val numberOfSeasons: Int? = null,
/** Whose season numbers `seasons` uses: "tvdb" (Sonarr) or "tmdb"; older servers send none. */
val seasonNumbering: String? = null,
/** True when TVDB splits the show differently from TMDB (the detail shows a note). */
val seasonNumberingDiffers: Boolean = false,
val studios: List<String>? = null,
val cast: List<CastMember> = emptyList(),
val recommendations: List<MediaItem> = emptyList(),
Expand Down Expand Up @@ -83,7 +87,7 @@ data class RequestOptions(
)

@Serializable
data class CreateRequestBody(val type: MediaType, val tmdbId: Int, val seasons: List<Int>? = null, val qualityProfileId: Int? = null, val rootFolder: String? = null)
data class CreateRequestBody(val type: MediaType, val tmdbId: Int, val seasons: List<Int>? = null, val qualityProfileId: Int? = null, val rootFolder: String? = null, val seasonNumbering: String? = null)

@Serializable
data class UserRef(val id: String = "", val name: String = "")
Expand Down Expand Up @@ -115,6 +119,7 @@ data class RequestView(
val createdAt: Long = 0,
val qualityProfileId: Int? = null,
val rootFolder: String? = null,
val seasonNumbering: String? = null,
/** Set when the request was imported from what Radarr/Sonarr already monitor. */
val source: String? = null,
/** What happened to the request and when, oldest first; a server older than 0.11 sends none. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ suspend fun TipsarrApi.flags(type: MediaType, id: Int): TitleFlags = get("/media
suspend fun TipsarrApi.requestOptions(type: MediaType): RequestOptions? =
try { get("/requests/options?type=${type.path()}", RequestOptions.serializer()) } catch (e: ApiError) { null }

suspend fun TipsarrApi.createRequest(type: MediaType, tmdbId: Int, seasons: List<Int>?, profileId: Int?, folder: String?): RequestRecord =
post("/requests", CreateRequestBody(type, tmdbId, seasons, profileId, folder), CreateRequestBody.serializer(), RequestView.serializer()).toRecord()
suspend fun TipsarrApi.createRequest(type: MediaType, tmdbId: Int, seasons: List<Int>?, profileId: Int?, folder: String?, seasonNumbering: String? = null): RequestRecord =
post("/requests", CreateRequestBody(type, tmdbId, seasons, profileId, folder, seasonNumbering), CreateRequestBody.serializer(), RequestView.serializer()).toRecord()

suspend fun TipsarrApi.myRequests(take: Int = 100): List<RequestRecord> =
get("/requests?filter=mine&take=$take", RequestList.serializer()).items.map { it.toRecord() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class MediaDetailModel(
suspend fun submitRequest(seasons: List<Int>?, profileId: Int?, folder: String?): RequestRecord {
busy = true
try {
val record = api.createRequest(route.type, route.tmdbId, seasons, profileId, folder)
val record = api.createRequest(route.type, route.tmdbId, seasons, profileId, folder, detail?.seasonNumbering?.takeIf { route.type == MediaType.Tv })
request = record
coveredSeasons = coveredSeasons + record.seasons
detail = detail?.copy(requestStatus = com.brebond.tipsarr.core.api.RequestStatus.Pending)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ fun MediaDetailScreen(model: MediaDetailModel, serverUrl: String, onBack: () ->
Banners(model)
Overview(detail)
if (detail.type == MediaType.Tv && model.regularSeasons.isNotEmpty()) {
SeasonList(model.api, model.route.tmdbId, model.regularSeasons, model.coveredSeasons, model.request?.state ?: RequestState.Requested)
SeasonList(model.api, model.route.tmdbId, model.regularSeasons, model.coveredSeasons, model.request?.state ?: RequestState.Requested, model.detail?.seasonNumberingDiffers == true)
}
Facts(detail)
if (detail.cast.isNotEmpty()) Cast(detail) { onOpenPerson(com.brebond.tipsarr.features.person.PersonRoute(it.id, it.name)) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ fun RequestSheet(model: MediaDetailModel, onDismiss: () -> Unit, onDone: (Reques
Text(stringResource(if (allSelected) R.string.req_select_none else R.string.req_select_all))
}
}
if (detail?.seasonNumberingDiffers == true) Text(stringResource(R.string.seasons_numbering_note), fontSize = Tokens.FontSize.footnote, color = Tokens.palette.mutedFg)
model.regularSeasons.forEach { season ->
val inLibrary = season.isComplete
val tracked = season.monitored && !inLibrary
Expand All @@ -142,7 +143,7 @@ fun RequestSheet(model: MediaDetailModel, onDismiss: () -> Unit, onDone: (Reques
)
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(season.name, fontSize = Tokens.FontSize.body, fontWeight = FontWeight.SemiBold, color = Tokens.palette.fg)
Text(context.plural("seasons_episodes", season.episodeCount), fontSize = Tokens.FontSize.footnote, color = Tokens.palette.mutedFg)
if (season.episodeCount > 0) Text(context.plural("seasons_episodes", season.episodeCount), fontSize = Tokens.FontSize.footnote, color = Tokens.palette.mutedFg)
}
season.badges.forEach { StatusBadge(it) }
if (covered && !inLibrary && !tracked) StatusBadge(model.request?.state ?: com.brebond.tipsarr.ui.RequestState.Requested)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import java.util.Locale

/** Seasons of a show; a tap opens the episodes of that season (loaded on first open), as on the web. */
@Composable
fun SeasonList(api: TipsarrApi, tmdbId: Int, seasons: List<SeasonInfo>, covered: Set<Int>, state: RequestState) {
fun SeasonList(api: TipsarrApi, tmdbId: Int, seasons: List<SeasonInfo>, covered: Set<Int>, state: RequestState, numberingDiffers: Boolean = false) {
val context = LocalContext.current
var open by remember { mutableStateOf<Int?>(null) }
val episodes = remember { mutableStateMapOf<Int, List<Episode>>() }
Expand All @@ -84,6 +84,7 @@ fun SeasonList(api: TipsarrApi, tmdbId: Int, seasons: List<SeasonInfo>, covered:
stringResource(R.string.seasons_title), fontSize = Tokens.FontSize.title3, fontWeight = FontWeight.Bold,
color = Tokens.palette.fg, modifier = Modifier.semantics { heading() },
)
if (numberingDiffers) Text(stringResource(R.string.seasons_numbering_note), fontSize = Tokens.FontSize.footnote, color = Tokens.palette.mutedFg)
seasons.forEach { season ->
val shape = RoundedCornerShape(Tokens.Radius.md)
Column(Modifier.fillMaxWidth().clip(shape).background(Tokens.palette.card).border(BorderStroke(1.dp, Tokens.palette.border), shape).animateContentSize()) {
Expand All @@ -93,9 +94,11 @@ fun SeasonList(api: TipsarrApi, tmdbId: Int, seasons: List<SeasonInfo>, covered:
) {
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(season.name, fontSize = Tokens.FontSize.body, fontWeight = FontWeight.SemiBold, color = Tokens.palette.fg, maxLines = 1, overflow = TextOverflow.Ellipsis)
val count = context.plural("seasons_episodes", season.episodeCount)
// 0 means the server does not know the count yet: show the year alone.
val count = season.episodeCount.takeIf { it > 0 }?.let { context.plural("seasons_episodes", it) }
val year = season.airDate?.takeIf { it.length >= 4 }?.take(4)
Text(if (year != null) "$count · $year" else count, fontSize = Tokens.FontSize.footnote, color = Tokens.palette.mutedFg)
val line = listOfNotNull(count, year).joinToString(" · ")
if (line.isNotEmpty()) Text(line, fontSize = Tokens.FontSize.footnote, color = Tokens.palette.mutedFg)
}
season.badges.forEach { StatusBadge(it) }
// The request's own state, unless the library already says the same (an available season showed "Available" twice).
Expand All @@ -114,6 +117,7 @@ fun SeasonList(api: TipsarrApi, tmdbId: Int, seasons: List<SeasonInfo>, covered:
when {
loading == season.number -> Text(stringResource(R.string.seasons_loading), fontSize = Tokens.FontSize.footnote, color = Tokens.palette.mutedFg)
message != null -> Text(message, fontSize = Tokens.FontSize.footnote, color = Tokens.palette.mutedFg)
episodes[season.number]?.isEmpty() == true -> Text(stringResource(R.string.seasons_no_episodes), fontSize = Tokens.FontSize.footnote, color = Tokens.palette.mutedFg)
else -> episodes[season.number].orEmpty().forEach { EpisodeRow(it) }
}
}
Expand Down
25 changes: 24 additions & 1 deletion android/app/src/main/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,12 @@ components:
rootFolder:
description: Root folder to use (omit for the default)
type: string
seasonNumbering:
description: "TV only: the detail's seasonNumbering the seasons were picked in; a different one than the server's now is 409 season_numbering_changed"
enum:
- tvdb
- tmdb
type: string
seasons:
description: TV only; empty means every season
items:
Expand Down Expand Up @@ -466,6 +472,15 @@ components:
runtimeMinutes:
format: int64
type: integer
seasonNumbering:
description: "Numbering of seasons: tvdb (Sonarr) or tmdb (fallback when Sonarr cannot tell)"
enum:
- tvdb
- tmdb
type: string
seasonNumberingDiffers:
description: TMDB splits this show's seasons differently from Sonarr (TVDB)
type: boolean
seasons:
items:
$ref: "#/components/schemas/Season"
Expand Down Expand Up @@ -1529,7 +1544,7 @@ components:
minimum: 0
type: integer
tvQuotaLimit:
description: Seasons of shows allowed per period, 0 = no limit
description: Seasons of shows allowed per period (one request costs at most this), 0 = no limit
format: int64
maximum: 100000
minimum: 0
Expand Down Expand Up @@ -2117,6 +2132,7 @@ components:
airDate:
type: string
episodeCount:
description: Episodes in the season; 0 = unknown
format: int64
type: integer
libraryEpisodes:
Expand Down Expand Up @@ -3202,6 +3218,12 @@ components:
rootFolder:
description: Chosen root folder (absent = the instance default)
type: string
seasonNumbering:
description: "Shows only: numbering of seasons, tvdb (Sonarr) or tmdb (made while Sonarr could not tell; translated when it is sent)"
enum:
- tvdb
- tmdb
type: string
seasonProgress:
description: "Shows only: completion per season"
items:
Expand Down Expand Up @@ -7373,6 +7395,7 @@ paths:
- media
/media/tv/{id}/seasons/{season}:
get:
description: "`season` follows the numbering of the show's detail (`seasonNumbering`): Sonarr's (TVDB) when it can tell. A season no source can list yet has no episodes."
operationId: tvSeason
parameters:
- in: path
Expand Down
8 changes: 8 additions & 0 deletions android/app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
<string name="error_quota_reached">Tu as atteint ta limite de demandes pour le moment.</string>
<string name="error_refresh_too_soon">Actualisé à l’instant, réessaie dans une minute.</string>
<string name="error_retype_key">Saisis à nouveau la clé API quand tu changes l’adresse.</string>
<string name="error_season_numbering_changed">Les saisons de cette série ont changé. Recharge la page et choisis-les à nouveau.</string>
<string name="error_setup_done">La configuration est déjà terminée.</string>
<string name="error_setup_token">Jeton d’installation requis : consulte le journal du serveur Tipsarr.</string>
<string name="error_setup_unreachable">Impossible de joindre un serveur Jellyfin à cette adresse.</string>
Expand Down Expand Up @@ -315,11 +316,13 @@
<string name="m_connect_title">Connecte-toi à ton serveur</string>
<string name="m_connect_unreachable">Impossible de joindre ce serveur. Vérifie l’adresse et ta connexion.</string>
<string name="m_detail_declined">Refusé par un admin.</string>
<string name="m_detail_no_episodes">La liste des épisodes apparaîtra quand Sonarr suivra cette série.</string>
<string name="m_detail_no_overview">Aucun résumé disponible.</string>
<string name="m_detail_open_jellyfin">Ouvrir dans Jellyfin</string>
<string name="m_detail_ratings_tv_hint">Seules les notes TMDB existent pour les séries.</string>
<string name="m_detail_request_again">Redemander</string>
<string name="m_detail_seasons_count">%1$s saison | %1$s saisons</string>
<string name="m_detail_seasons_numbering_note">Les saisons suivent la numérotation de Sonarr (TVDB), qui découpe cette série autrement que TMDB.</string>
<string name="m_detail_share">Partager</string>
<string name="m_discover_chip_for_you">Pour toi</string>
<string name="m_discover_chip_movies">Films</string>
Expand Down Expand Up @@ -708,6 +711,7 @@
<string name="req_retry">Réessayer</string>
<string name="req_root_folder">Dossier</string>
<string name="req_season_badge">Saison %1$s</string>
<string name="req_season_range">Saisons %1$s à %2$s</string>
<string name="req_seasons">Saison %1$s | Saisons %1$s</string>
<string name="req_select_all">Tout sélectionner</string>
<string name="req_select_none">Tout désélectionner</string>
Expand Down Expand Up @@ -772,7 +776,9 @@
<string name="search_tags">Mots-clés :</string>
<string name="seasons_episodes">%1$s épisode | %1$s épisodes</string>
<string name="seasons_loading">Chargement des épisodes…</string>
<string name="seasons_no_episodes">La liste des épisodes apparaîtra quand Sonarr suivra cette série.</string>
<string name="seasons_no_preview">Pas d’aperçu</string>
<string name="seasons_numbering_note">Les saisons suivent la numérotation de Sonarr (TVDB), qui découpe cette série autrement que TMDB.</string>
<string name="seasons_season">Saison %1$s</string>
<string name="seasons_title">Saisons</string>
<string name="srv_job_boxoffice_stored">%1$s classements enregistrés</string>
Expand All @@ -796,6 +802,8 @@
<string name="srv_job_trailers_synced">%1$s fichiers prêts, %2$s en attente, %3$s en échec</string>
<string name="srv_job_ytdlp_current">yt-dlp est à jour (%1$s)</string>
<string name="srv_job_ytdlp_updated">yt-dlp mis à jour de %1$s vers %2$s</string>
<string name="srv_season">Saison %1$s</string>
<string name="srv_season_specials">Épisodes spéciaux</string>
<string name="state_approved">Approuvé</string>
<string name="state_available">Disponible</string>
<string name="state_declined">Refusé</string>
Expand Down
8 changes: 8 additions & 0 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
<string name="error_quota_reached">You have reached your request limit for now.</string>
<string name="error_refresh_too_soon">Refreshed a moment ago, try again in a minute.</string>
<string name="error_retype_key">Enter the API key again when you change the address.</string>
<string name="error_season_numbering_changed">The seasons of this show changed. Reload and pick them again.</string>
<string name="error_setup_done">Setup has already been completed.</string>
<string name="error_setup_token">Setup token required: see the Tipsarr server log.</string>
<string name="error_setup_unreachable">Cannot reach a Jellyfin server at that address.</string>
Expand Down Expand Up @@ -315,11 +316,13 @@
<string name="m_connect_title">Connect to your server</string>
<string name="m_connect_unreachable">Can’t reach this server. Check the address and your connection.</string>
<string name="m_detail_declined">Declined by an admin.</string>
<string name="m_detail_no_episodes">The episode list appears once Sonarr tracks this show.</string>
<string name="m_detail_no_overview">No overview available.</string>
<string name="m_detail_open_jellyfin">Open in Jellyfin</string>
<string name="m_detail_ratings_tv_hint">Only TMDB scores exist for TV shows.</string>
<string name="m_detail_request_again">Request again</string>
<string name="m_detail_seasons_count">%1$s season | %1$s seasons</string>
<string name="m_detail_seasons_numbering_note">Seasons follow Sonarr’s numbering (TVDB), which splits this show differently from TMDB.</string>
<string name="m_detail_share">Share</string>
<string name="m_discover_chip_for_you">For you</string>
<string name="m_discover_chip_movies">Movies</string>
Expand Down Expand Up @@ -708,6 +711,7 @@
<string name="req_retry">Retry</string>
<string name="req_root_folder">Folder</string>
<string name="req_season_badge">Season %1$s</string>
<string name="req_season_range">Seasons %1$s–%2$s</string>
<string name="req_seasons">Season %1$s | Seasons %1$s</string>
<string name="req_select_all">Select all</string>
<string name="req_select_none">Select none</string>
Expand Down Expand Up @@ -772,7 +776,9 @@
<string name="search_tags">Tags:</string>
<string name="seasons_episodes">%1$s episode | %1$s episodes</string>
<string name="seasons_loading">Loading episodes…</string>
<string name="seasons_no_episodes">The episode list appears once Sonarr tracks this show.</string>
<string name="seasons_no_preview">No preview</string>
<string name="seasons_numbering_note">Seasons follow Sonarr’s numbering (TVDB), which splits this show differently from TMDB.</string>
<string name="seasons_season">Season %1$s</string>
<string name="seasons_title">Seasons</string>
<string name="srv_job_boxoffice_stored">%1$s charts stored</string>
Expand All @@ -796,6 +802,8 @@
<string name="srv_job_trailers_synced">%1$s trailer files ready, %2$s waiting, %3$s failed</string>
<string name="srv_job_ytdlp_current">yt-dlp is up to date (%1$s)</string>
<string name="srv_job_ytdlp_updated">yt-dlp updated from %1$s to %2$s</string>
<string name="srv_season">Season %1$s</string>
<string name="srv_season_specials">Specials</string>
<string name="state_approved">Approved</string>
<string name="state_available">Available</string>
<string name="state_declined">Declined</string>
Expand Down
Loading
Loading