mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Make runtime nullable
This commit is contained in:
parent
b6df0ba2b1
commit
c0e6012d70
@ -48,7 +48,8 @@ public interface IWatchStatusRepository
|
|||||||
Guid movieId,
|
Guid movieId,
|
||||||
Guid userId,
|
Guid userId,
|
||||||
WatchStatus status,
|
WatchStatus status,
|
||||||
int? watchedTime
|
int? watchedTime,
|
||||||
|
int? percent
|
||||||
);
|
);
|
||||||
|
|
||||||
Task DeleteMovieStatus(Guid movieId, Guid userId);
|
Task DeleteMovieStatus(Guid movieId, Guid userId);
|
||||||
@ -67,7 +68,8 @@ public interface IWatchStatusRepository
|
|||||||
Guid episodeId,
|
Guid episodeId,
|
||||||
Guid userId,
|
Guid userId,
|
||||||
WatchStatus status,
|
WatchStatus status,
|
||||||
int? watchedTime
|
int? watchedTime,
|
||||||
|
int? percent
|
||||||
);
|
);
|
||||||
|
|
||||||
Task DeleteEpisodeStatus(Guid episodeId, Guid userId);
|
Task DeleteEpisodeStatus(Guid episodeId, Guid userId);
|
||||||
|
@ -152,7 +152,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// How long is this episode? (in minutes)
|
/// How long is this episode? (in minutes)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Runtime { get; set; }
|
public int? Runtime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The release date of this episode. It can be null if unknown.
|
/// The release date of this episode. It can be null if unknown.
|
||||||
|
@ -99,7 +99,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// How long is this movie? (in minutes)
|
/// How long is this movie? (in minutes)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Runtime { get; set; }
|
public int? Runtime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The date this movie aired.
|
/// The date this movie aired.
|
||||||
|
@ -236,14 +236,14 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
Guid movieId,
|
Guid movieId,
|
||||||
Guid userId,
|
Guid userId,
|
||||||
WatchStatus status,
|
WatchStatus status,
|
||||||
int? watchedTime
|
int? watchedTime,
|
||||||
|
int? percent
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Movie movie = await _movies.Get(movieId);
|
Movie movie = await _movies.Get(movieId);
|
||||||
int? percent =
|
|
||||||
watchedTime != null && movie.Runtime > 0
|
if (percent == null && watchedTime != null && movie.Runtime > 0)
|
||||||
? (int)Math.Round(watchedTime.Value / (movie.Runtime * 60f) * 100f)
|
percent = (int)Math.Round(watchedTime.Value / (movie.Runtime.Value * 60f) * 100f);
|
||||||
: null;
|
|
||||||
|
|
||||||
if (percent < MinWatchPercent)
|
if (percent < MinWatchPercent)
|
||||||
return null;
|
return null;
|
||||||
@ -259,6 +259,12 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
"Can't have a watched time if the status is not watching."
|
"Can't have a watched time if the status is not watching."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (watchedTime.HasValue != percent.HasValue)
|
||||||
|
throw new ValidationException(
|
||||||
|
"Can't specify watched time without specifing percent (or vise-versa)."
|
||||||
|
+ "Percent could not be guessed since duration is unknown."
|
||||||
|
);
|
||||||
|
|
||||||
MovieWatchStatus ret =
|
MovieWatchStatus ret =
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
@ -463,14 +469,14 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
Guid episodeId,
|
Guid episodeId,
|
||||||
Guid userId,
|
Guid userId,
|
||||||
WatchStatus status,
|
WatchStatus status,
|
||||||
int? watchedTime
|
int? watchedTime,
|
||||||
|
int? percent
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Episode episode = await _database.Episodes.FirstAsync(x => x.Id == episodeId);
|
Episode episode = await _database.Episodes.FirstAsync(x => x.Id == episodeId);
|
||||||
int? percent =
|
|
||||||
watchedTime != null && episode.Runtime > 0
|
if (percent == null && watchedTime != null && episode.Runtime > 0)
|
||||||
? (int)Math.Round(watchedTime.Value / (episode.Runtime * 60f) * 100f)
|
percent = (int)Math.Round(watchedTime.Value / (episode.Runtime.Value * 60f) * 100f);
|
||||||
: null;
|
|
||||||
|
|
||||||
if (percent < MinWatchPercent)
|
if (percent < MinWatchPercent)
|
||||||
return null;
|
return null;
|
||||||
@ -486,6 +492,12 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
"Can't have a watched time if the status is not watching."
|
"Can't have a watched time if the status is not watching."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (watchedTime.HasValue != percent.HasValue)
|
||||||
|
throw new ValidationException(
|
||||||
|
"Can't specify watched time without specifing percent (or vise-versa)."
|
||||||
|
+ "Percent could not be guessed since duration is unknown."
|
||||||
|
);
|
||||||
|
|
||||||
EpisodeWatchStatus ret =
|
EpisodeWatchStatus ret =
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
|
@ -147,7 +147,8 @@ namespace Kyoo.Core.Api
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
||||||
/// <param name="status">The new watch status.</param>
|
/// <param name="status">The new watch status.</param>
|
||||||
/// <param name="watchedTime">Where the user stopped watching.</param>
|
/// <param name="watchedTime">Where the user stopped watching (in seconds).</param>
|
||||||
|
/// <param name="percent">Where the user stopped watching (in percent).</param>
|
||||||
/// <returns>The newly set status.</returns>
|
/// <returns>The newly set status.</returns>
|
||||||
/// <response code="200">The status has been set</response>
|
/// <response code="200">The status has been set</response>
|
||||||
/// <response code="204">The status was not considered impactfull enough to be saved (less then 5% of watched for example).</response>
|
/// <response code="204">The status was not considered impactfull enough to be saved (less then 5% of watched for example).</response>
|
||||||
@ -161,7 +162,8 @@ namespace Kyoo.Core.Api
|
|||||||
public async Task<EpisodeWatchStatus?> SetWatchStatus(
|
public async Task<EpisodeWatchStatus?> SetWatchStatus(
|
||||||
Identifier identifier,
|
Identifier identifier,
|
||||||
WatchStatus status,
|
WatchStatus status,
|
||||||
int? watchedTime
|
int? watchedTime,
|
||||||
|
int? percent
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Guid id = await identifier.Match(
|
Guid id = await identifier.Match(
|
||||||
@ -170,7 +172,7 @@ namespace Kyoo.Core.Api
|
|||||||
);
|
);
|
||||||
return await _libraryManager
|
return await _libraryManager
|
||||||
.WatchStatus
|
.WatchStatus
|
||||||
.SetEpisodeStatus(id, User.GetIdOrThrow(), status, watchedTime);
|
.SetEpisodeStatus(id, User.GetIdOrThrow(), status, watchedTime, percent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -196,6 +196,7 @@ namespace Kyoo.Core.Api
|
|||||||
/// <param name="identifier">The ID or slug of the <see cref="Movie"/>.</param>
|
/// <param name="identifier">The ID or slug of the <see cref="Movie"/>.</param>
|
||||||
/// <param name="status">The new watch status.</param>
|
/// <param name="status">The new watch status.</param>
|
||||||
/// <param name="watchedTime">Where the user stopped watching.</param>
|
/// <param name="watchedTime">Where the user stopped watching.</param>
|
||||||
|
/// <param name="percent">Where the user stopped watching (in percent).</param>
|
||||||
/// <returns>The newly set status.</returns>
|
/// <returns>The newly set status.</returns>
|
||||||
/// <response code="200">The status has been set</response>
|
/// <response code="200">The status has been set</response>
|
||||||
/// <response code="204">The status was not considered impactfull enough to be saved (less then 5% of watched for example).</response>
|
/// <response code="204">The status was not considered impactfull enough to be saved (less then 5% of watched for example).</response>
|
||||||
@ -210,7 +211,8 @@ namespace Kyoo.Core.Api
|
|||||||
public async Task<MovieWatchStatus?> SetWatchStatus(
|
public async Task<MovieWatchStatus?> SetWatchStatus(
|
||||||
Identifier identifier,
|
Identifier identifier,
|
||||||
WatchStatus status,
|
WatchStatus status,
|
||||||
int? watchedTime
|
int? watchedTime,
|
||||||
|
int? percent
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Guid id = await identifier.Match(
|
Guid id = await identifier.Match(
|
||||||
@ -219,7 +221,7 @@ namespace Kyoo.Core.Api
|
|||||||
);
|
);
|
||||||
return await _libraryManager
|
return await _libraryManager
|
||||||
.WatchStatus
|
.WatchStatus
|
||||||
.SetMovieStatus(id, User.GetIdOrThrow(), status, watchedTime);
|
.SetMovieStatus(id, User.GetIdOrThrow(), status, watchedTime, percent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -49,7 +49,7 @@ export const BaseEpisodeP = withImages(
|
|||||||
/**
|
/**
|
||||||
* How long is this movie? (in minutes).
|
* How long is this movie? (in minutes).
|
||||||
*/
|
*/
|
||||||
runtime: z.number().int(),
|
runtime: z.number().int().nullable(),
|
||||||
/**
|
/**
|
||||||
* The release date of this episode. It can be null if unknown.
|
* The release date of this episode. It can be null if unknown.
|
||||||
*/
|
*/
|
||||||
|
@ -61,7 +61,7 @@ export const MovieP = withImages(
|
|||||||
/**
|
/**
|
||||||
* How long is this movie? (in minutes).
|
* How long is this movie? (in minutes).
|
||||||
*/
|
*/
|
||||||
runtime: z.number().int(),
|
runtime: z.number().int().nullable(),
|
||||||
/**
|
/**
|
||||||
* The date this movie aired. It can also be null if this is unknown.
|
* The date this movie aired. It can also be null if this is unknown.
|
||||||
*/
|
*/
|
||||||
|
@ -106,6 +106,10 @@ export const WatchInfoP = z.object({
|
|||||||
* The sha1 of the video file.
|
* The sha1 of the video file.
|
||||||
*/
|
*/
|
||||||
sha: z.string(),
|
sha: z.string(),
|
||||||
|
/**
|
||||||
|
* The duration of the video (in seconds).
|
||||||
|
*/
|
||||||
|
length: z.number(),
|
||||||
/**
|
/**
|
||||||
* The internal path of the video file.
|
* The internal path of the video file.
|
||||||
*/
|
*/
|
||||||
|
@ -57,7 +57,8 @@ export const episodeDisplayNumber = (
|
|||||||
return def;
|
return def;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const displayRuntime = (runtime: number) => {
|
export const displayRuntime = (runtime: number | null) => {
|
||||||
|
if (!runtime) return null;
|
||||||
if (runtime < 60) return `${runtime}min`;
|
if (runtime < 60) return `${runtime}min`;
|
||||||
return `${Math.floor(runtime / 60)}h${runtime % 60}`;
|
return `${Math.floor(runtime / 60)}h${runtime % 60}`;
|
||||||
};
|
};
|
||||||
@ -300,22 +301,19 @@ export const EpisodeLine = ({
|
|||||||
)}
|
)}
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<View {...css({ flexDirection: "row", alignItems: "center" })}>
|
<View {...css({ flexDirection: "row", alignItems: "center" })}>
|
||||||
{isLoading ||
|
<Skeleton>
|
||||||
(runtime && (
|
{isLoading || (
|
||||||
<Skeleton>
|
<SubP>
|
||||||
{isLoading || (
|
{/* Source https://www.i18next.com/translation-function/formatting#datetime */}
|
||||||
<SubP>
|
{[
|
||||||
{/* Source https://www.i18next.com/translation-function/formatting#datetime */}
|
releaseDate ? t("{{val, datetime}}", { val: releaseDate }) : null,
|
||||||
{[
|
displayRuntime(runtime),
|
||||||
releaseDate ? t("{{val, datetime}}", { val: releaseDate }) : null,
|
]
|
||||||
displayRuntime(runtime),
|
.filter((item) => item != null)
|
||||||
]
|
.join(" · ")}
|
||||||
.filter((item) => item != null)
|
</SubP>
|
||||||
.join(" · ")}
|
)}
|
||||||
</SubP>
|
</Skeleton>
|
||||||
)}
|
|
||||||
</Skeleton>
|
|
||||||
))}
|
|
||||||
{slug && watchedStatus !== undefined && (
|
{slug && watchedStatus !== undefined && (
|
||||||
<EpisodesContext
|
<EpisodesContext
|
||||||
slug={slug}
|
slug={slug}
|
||||||
|
@ -127,7 +127,7 @@ export const Player = ({ slug, type }: { slug: string; type: "episode" | "movie"
|
|||||||
next={next}
|
next={next}
|
||||||
previous={previous}
|
previous={previous}
|
||||||
/>
|
/>
|
||||||
{data && <WatchStatusObserver type={type} slug={data.slug} />}
|
{data && info && <WatchStatusObserver type={type} slug={data.slug} duration={info.length} />}
|
||||||
<View
|
<View
|
||||||
{...css({
|
{...css({
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
|
@ -28,9 +28,11 @@ import { playAtom, progressAtom } from "./state";
|
|||||||
export const WatchStatusObserver = ({
|
export const WatchStatusObserver = ({
|
||||||
type,
|
type,
|
||||||
slug,
|
slug,
|
||||||
|
duration,
|
||||||
}: {
|
}: {
|
||||||
type: "episode" | "movie";
|
type: "episode" | "movie";
|
||||||
slug: string;
|
slug: string;
|
||||||
|
duration: number;
|
||||||
}) => {
|
}) => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -47,9 +49,10 @@ export const WatchStatusObserver = ({
|
|||||||
params: {
|
params: {
|
||||||
status: WatchStatusV.Watching,
|
status: WatchStatusV.Watching,
|
||||||
watchedTime: Math.round(seconds),
|
watchedTime: Math.round(seconds),
|
||||||
|
percent: Math.round((seconds / duration) * 100),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[_mutate, type, slug],
|
[_mutate, type, slug, duration],
|
||||||
);
|
);
|
||||||
const readProgress = useAtomCallback(
|
const readProgress = useAtomCallback(
|
||||||
useCallback((get) => {
|
useCallback((get) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user