diff --git a/back/src/Kyoo.Abstractions/Models/News.cs b/back/src/Kyoo.Abstractions/Models/News.cs
index 5c30915f..f42cf624 100644
--- a/back/src/Kyoo.Abstractions/Models/News.cs
+++ b/back/src/Kyoo.Abstractions/Models/News.cs
@@ -135,6 +135,12 @@ namespace Kyoo.Abstractions.Models
///
public int? AbsoluteNumber { get; set; }
+ ///
+ /// A simple summary of informations about the show of this episode
+ /// (this is specially useful since news can't have includes).
+ ///
+ public ShowInfo? Show { get; set; }
+
///
/// Is the item a a movie or an episode?
///
@@ -148,5 +154,23 @@ namespace Kyoo.Abstractions.Models
Direct = $"/video/{Kind.ToString().ToLower()}/{Slug}/direct",
Hls = $"/video/{Kind.ToString().ToLower()}/{Slug}/master.m3u8",
};
+
+ ///
+ /// A simple summary of informations about the show of this episode
+ /// (this is specially useful since news can't have includes).
+ ///
+ public class ShowInfo : IResource
+ {
+ ///
+ public int Id { get; set; }
+
+ ///
+ public string Slug { get; set; }
+
+ ///
+ /// The title of this show.
+ ///
+ public string Name { get; set; }
+ }
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
index 98dd1d61..17b60ee3 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
@@ -19,7 +19,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs
index 557ca197..1c53ae89 100644
--- a/back/src/Kyoo.Postgresql/DatabaseContext.cs
+++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs
@@ -360,6 +360,9 @@ namespace Kyoo.Postgresql
.Ignore(x => x.Links);
modelBuilder.Entity()
.Ignore(x => x.Links);
+
+ modelBuilder.Entity()
+ .OwnsOne(x => x.Show);
}
///
diff --git a/back/src/Kyoo.Postgresql/Migrations/20231029233109_news.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20231029233109_news.Designer.cs
index b34f5094..3464cf84 100644
--- a/back/src/Kyoo.Postgresql/Migrations/20231029233109_news.Designer.cs
+++ b/back/src/Kyoo.Postgresql/Migrations/20231029233109_news.Designer.cs
@@ -1275,10 +1275,41 @@ namespace Kyoo.Postgresql.Migrations
.HasConstraintName("fk_news_news_id");
});
+ b.OwnsOne("Kyoo.Abstractions.Models.ShowInfo", "Show", b1 =>
+ {
+ b1.Property("NewsId")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Id")
+ .HasColumnType("integer")
+ .HasColumnName("show_info_id");
+
+ b1.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("show_info_name");
+
+ b1.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("show_info_slug");
+
+ b1.HasKey("NewsId");
+
+ b1.ToTable("news");
+
+ b1.WithOwner()
+ .HasForeignKey("NewsId")
+ .HasConstraintName("fk_news_news_id");
+ });
+
b.Navigation("Logo");
b.Navigation("Poster");
+ b.Navigation("Show");
+
b.Navigation("Thumbnail");
});
diff --git a/back/src/Kyoo.Postgresql/Migrations/20231029233109_news.cs b/back/src/Kyoo.Postgresql/Migrations/20231029233109_news.cs
index a08ef3a5..926f1f04 100644
--- a/back/src/Kyoo.Postgresql/Migrations/20231029233109_news.cs
+++ b/back/src/Kyoo.Postgresql/Migrations/20231029233109_news.cs
@@ -38,14 +38,15 @@ namespace Kyoo.Postgresql.Migrations
e.id, e.slug, e.name, NULL AS tagline, '{}' AS aliases, e.path, e.overview, '{}' AS tags, '{}' AS genres,
NULL AS status, e.release_date AS air_date, e.poster_source, e.poster_blurhash, e.thumbnail_source, e.thumbnail_blurhash,
e.logo_source,e.logo_blurhash, NULL AS trailer, e.external_id, e.season_number, e.episode_number, e.absolute_number,
- 'episode'::news_kind AS kind, e.added_date
+ 'episode'::news_kind AS kind, e.added_date, s.id AS show_id, s.slug AS show_slug, s.name AS show_name
FROM episodes AS e
+ LEFT JOIN shows AS s ON e.show_id = s.id
UNION ALL
SELECT
-m.id, m.slug, m.name, m.tagline, m.aliases, m.path, m.overview, m.tags, m.genres,
m.status, m.air_date, m.poster_source, m.poster_blurhash, m.thumbnail_source, m.thumbnail_blurhash,
m.logo_source, m.logo_blurhash, m.trailer, m.external_id, NULL AS season_number, NULL AS episode_number, NULL as absolute_number,
- 'movie'::news_kind AS kind, m.added_date
+ 'movie'::news_kind AS kind, m.added_date, NULL AS show_id, NULL AS show_slug, NULL AS show_name
FROM movies AS m
");
}
diff --git a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs
index 51a155f7..4f4bb873 100644
--- a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs
+++ b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs
@@ -1,4 +1,4 @@
-//
+//
using System;
using System.Collections.Generic;
using Kyoo.Abstractions.Models;
@@ -1272,10 +1272,41 @@ namespace Kyoo.Postgresql.Migrations
.HasConstraintName("fk_news_news_id");
});
+ b.OwnsOne("Kyoo.Abstractions.Models.ShowInfo", "Show", b1 =>
+ {
+ b1.Property("NewsId")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b1.Property("Id")
+ .HasColumnType("integer")
+ .HasColumnName("show_info_id");
+
+ b1.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("show_info_name");
+
+ b1.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("show_info_slug");
+
+ b1.HasKey("NewsId");
+
+ b1.ToTable("news");
+
+ b1.WithOwner()
+ .HasForeignKey("NewsId")
+ .HasConstraintName("fk_news_news_id");
+ });
+
b.Navigation("Logo");
b.Navigation("Poster");
+ b.Navigation("Show");
+
b.Navigation("Thumbnail");
});
diff --git a/front/packages/models/src/resources/episode.ts b/front/packages/models/src/resources/episode.ts
index ae9bc1b4..282db100 100644
--- a/front/packages/models/src/resources/episode.ts
+++ b/front/packages/models/src/resources/episode.ts
@@ -24,7 +24,7 @@ import { withImages, imageFn } from "../traits";
import { ResourceP } from "../traits/resource";
import { ShowP } from "./show";
-const BaseEpisodeP = withImages(
+export const BaseEpisodeP = withImages(
ResourceP.extend({
/**
* The season in witch this episode is in.
@@ -73,7 +73,10 @@ const BaseEpisodeP = withImages(
}),
}),
"episodes",
-);
+).transform((x) => ({
+ ...x,
+ href: `/watch/${x.slug}`,
+}));
export const EpisodeP = BaseEpisodeP.and(
z.object({
diff --git a/front/packages/models/src/resources/index.ts b/front/packages/models/src/resources/index.ts
index 5f06357f..310781fb 100644
--- a/front/packages/models/src/resources/index.ts
+++ b/front/packages/models/src/resources/index.ts
@@ -19,6 +19,7 @@
*/
export * from "./library-item";
+export * from "./news";
export * from "./show";
export * from "./movie";
export * from "./collection";
diff --git a/front/packages/models/src/resources/news.ts b/front/packages/models/src/resources/news.ts
new file mode 100644
index 00000000..521b0b00
--- /dev/null
+++ b/front/packages/models/src/resources/news.ts
@@ -0,0 +1,55 @@
+/*
+ * Kyoo - A portable and vast media library solution.
+ * Copyright (c) Kyoo.
+ *
+ * See AUTHORS.md and LICENSE file in the project root for full license information.
+ *
+ * Kyoo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * Kyoo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Kyoo. If not, see .
+ */
+
+import { z } from "zod";
+import { MovieP } from "./movie";
+import { BaseEpisodeP } from "./episode";
+import { ResourceP } from "../traits/resource";
+
+/**
+ * The type of item, ether a a movie or an episode.
+ */
+export enum NewsKind {
+ Episode = "Episode",
+ Movie = "Movie",
+}
+
+export const NewsP = z.union([
+ /*
+ * Either an episode
+ */
+ BaseEpisodeP.and(
+ z.object({
+ kind: z.literal(NewsKind.Episode),
+ show: ResourceP.extend({
+ name: z.string(),
+ }),
+ }),
+ ),
+ /*
+ * Or a Movie
+ */
+ MovieP.and(z.object({ kind: z.literal(NewsKind.Movie) })),
+]);
+
+/**
+ * A new item added to kyoo.
+ */
+export type News = z.infer;
diff --git a/front/packages/ui/src/home/genre.tsx b/front/packages/ui/src/home/genre.tsx
index 6c45cbe4..c0b90d6c 100644
--- a/front/packages/ui/src/home/genre.tsx
+++ b/front/packages/ui/src/home/genre.tsx
@@ -77,7 +77,6 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
query={query}
layout={{ ...ItemGrid.layout, layout: "horizontal" }}
empty={displayEmpty.current ? t("home.none") : undefined}
- headerProps={{ title: genre, displayEmpty: displayEmpty.current }}
>
{(x, i) => {
// only display empty list if a loading as been displayed (not durring ssr)
diff --git a/front/packages/ui/src/home/index.tsx b/front/packages/ui/src/home/index.tsx
index ac909b76..88f9c8eb 100644
--- a/front/packages/ui/src/home/index.tsx
+++ b/front/packages/ui/src/home/index.tsx
@@ -22,10 +22,11 @@ import { Genre, ItemKind, QueryPage } from "@kyoo/models";
import { Fetch } from "../fetch";
import { Header } from "./header";
import { DefaultLayout } from "../layout";
-import { ScrollView, View } from "react-native";
+import { ScrollView } from "react-native";
import { GenreGrid } from "./genre";
import { Recommanded } from "./recommanded";
import { VerticalRecommanded } from "./vertical";
+import { NewsList } from "./news";
export const HomePage: QueryPage<{}, Genre> = ({ randomItems }) => {
return (
@@ -43,7 +44,7 @@ export const HomePage: QueryPage<{}, Genre> = ({ randomItems }) => {
/>
)}
- {/* */}
+
{randomItems
.filter((_, i) => i < 2)
.map((x) => (
@@ -71,6 +72,7 @@ HomePage.getLayout = { Layout: DefaultLayout, props: { transparent: true } };
HomePage.getFetchUrls = () => [
Header.query(),
+ NewsList.query(),
...Object.values(Genre).map((x) => GenreGrid.query(x)),
Recommanded.query(),
VerticalRecommanded.query(),
diff --git a/front/packages/ui/src/home/news.tsx b/front/packages/ui/src/home/news.tsx
new file mode 100644
index 00000000..75ece79a
--- /dev/null
+++ b/front/packages/ui/src/home/news.tsx
@@ -0,0 +1,84 @@
+/*
+ * Kyoo - A portable and vast media library solution.
+ * Copyright (c) Kyoo.
+ *
+ * See AUTHORS.md and LICENSE file in the project root for full license information.
+ *
+ * Kyoo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * Kyoo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Kyoo. If not, see .
+ */
+
+import {
+ Genre,
+ ItemKind,
+ News,
+ NewsKind,
+ NewsP,
+ QueryIdentifier,
+ getDisplayDate,
+} from "@kyoo/models";
+import { H3, IconButton, ts } from "@kyoo/primitives";
+import { ReactElement, forwardRef, useRef } from "react";
+import { View } from "react-native";
+import { px, useYoshiki } from "yoshiki/native";
+import { ItemGrid } from "../browse/grid";
+import ChevronLeft from "@material-symbols/svg-400/rounded/chevron_left-fill.svg";
+import ChevronRight from "@material-symbols/svg-400/rounded/chevron_right-fill.svg";
+import { InfiniteFetch, InfiniteFetchList } from "../fetch-infinite";
+import { useTranslation } from "react-i18next";
+import { Header } from "./genre";
+import { EpisodeBox } from "../details/episode";
+
+export const NewsList = () => {
+ const { t } = useTranslation();
+
+ return (
+ <>
+
+
+ {(x, i) =>
+ x.kind === NewsKind.Movie || (x.isLoading && i % 2) ? (
+
+ ) : (
+
+ )
+ }
+
+ >
+ );
+};
+
+NewsList.query = (): QueryIdentifier => ({
+ parser: NewsP,
+ infinite: true,
+ path: ["news"],
+ params: {
+ // Limit the inital numbers of items
+ limit: 10,
+ },
+});
diff --git a/front/translations/en.json b/front/translations/en.json
index e18bb374..e4bac99c 100644
--- a/front/translations/en.json
+++ b/front/translations/en.json
@@ -1,6 +1,7 @@
{
"home": {
"recommanded": "Recommanded",
+ "news": "News",
"info": "See more",
"none": "No episodes"
},
diff --git a/front/translations/fr.json b/front/translations/fr.json
index bd26690e..47b85d2c 100644
--- a/front/translations/fr.json
+++ b/front/translations/fr.json
@@ -1,6 +1,7 @@
{
"home": {
"recommanded": "Recommandé",
+ "news": "Nouveautés",
"info": "Voir plus",
"none": "Aucun episode"
},