Initial commit

This commit is contained in:
ExostFlash 2025-11-02 16:10:38 +01:00
parent 17ec8a0045
commit aad4686d48
15 changed files with 439 additions and 52 deletions

1
.idea/gradle.xml generated
View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>

View file

@ -27,7 +27,7 @@ tasks.withType<JavaCompile> {
application { application {
mainModule.set("dev.mecdu.javafx") mainModule.set("dev.mecdu.javafx")
mainClass.set("dev.mecdu.javafx.HelloApplication") mainClass.set("dev.mecdu.javafx.HomeApplication")
} }
javafx { javafx {
@ -40,6 +40,10 @@ dependencies {
implementation("org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0") implementation("org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}") testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
implementation("com.squareup.okhttp3:okhttp:+")
compileOnly("org.projectlombok:lombok:+")
annotationProcessor("org.projectlombok:lombok:1.+")
implementation("com.google.code.gson:gson:+")
} }
tasks.withType<Test> { tasks.withType<Test> {

View file

@ -1,19 +0,0 @@
package dev.mecdu.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
}

View file

@ -1,14 +0,0 @@
package dev.mecdu.javafx;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class HelloController {
@FXML
private Label welcomeText;
@FXML
protected void onHelloButtonClick() {
welcomeText.setText("Welcome to JavaFX Application!");
}
}

View file

@ -0,0 +1,30 @@
package dev.mecdu.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.Objects;
public class HomeApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HomeApplication.class.getResource("home.fxml"));
Parent root = fxmlLoader.load();
// Récupère le contrôleur si tu veux lui passer des infos
// HomeController controller = loader.getController();
Scene scene = new Scene(root, 1000, 700);
stage.setTitle("Streamify 🎬");
stage.getIcons().add(new Image(Objects.requireNonNull(HomeApplication.class.getResourceAsStream("/dev/mecdu/javafx/logo.png"))));
stage.setScene(scene);
stage.show();
}
}

View file

@ -4,6 +4,6 @@ import javafx.application.Application;
public class Launcher { public class Launcher {
public static void main(String[] args) { public static void main(String[] args) {
Application.launch(HelloApplication.class, args); Application.launch(HomeApplication.class, args);
} }
} }

View file

@ -0,0 +1,51 @@
package dev.mecdu.javafx.api;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import dev.mecdu.javafx.model.Program;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
public class StreamifyApiClient {
private static final String BASE_URL = "http://api-stream.apps.lehub.tf/api"; // à adapter selon ton API
private final HttpClient httpClient;
private final Gson gson;
public StreamifyApiClient() {
this.httpClient = HttpClient.newHttpClient();
this.gson = new Gson();
}
public List<Program> getPrograms() throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/v1/program"))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IOException("Erreur API Streamify : " + response.statusCode());
}
// parser l'objet JSON
JsonObject obj = gson.fromJson(response.body(), JsonObject.class);
// récupérer le tableau programs
JsonElement programsElement = obj.get("programs");
Type listType = new TypeToken<List<Program>>() {}.getType();
List<Program> programs = gson.fromJson(programsElement, listType);
return programs;
}
}

View file

@ -0,0 +1,76 @@
package dev.mecdu.javafx.model;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.util.UUID;
@Data
public class Program {
private int id;
@SerializedName("tmdb_id")
private int tmdbId;
private UUID uuid;
@SerializedName("programs_id")
private int programId;
@SerializedName("fr_title")
private String titleFR;
@SerializedName("original_title")
private String title;
private String types; // movies - tv
@SerializedName("released_at")
private int releasedAt;
private String genders;
private String origins;
private String directors;
private String actors;
private String lengths; // 01:44:00
private String resum;
@SerializedName("trailer_link")
private String trailerLink;
@SerializedName("portrait_img_link")
private String portraitLink;
@SerializedName("landscape_img_link")
private String landscapeLink;
private String format; // webm - mp4
private int active;
@SerializedName("created_at")
private String createdAt;
@SerializedName("updated_at")
private String updatedAt;
public enum Format {
mp4,
webm
}
public enum Types {
movies,
tv
}
@Override
public String toString() {
return title; // utile si tu ne mets pas de cellFactory
}
}

View file

@ -0,0 +1,8 @@
package dev.mecdu.javafx.model;
import lombok.Data;
@Data
public class Programs {
private Program[] programs;
}

View file

@ -0,0 +1,159 @@
package dev.mecdu.javafx.ui;
import dev.mecdu.javafx.api.StreamifyApiClient;
import dev.mecdu.javafx.model.Program;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
public class HomeController implements Initializable {
public VBox proSection;
public VBox tvSection;
public VBox moviesSection;
@FXML
private ListView<Program> movieList;
@FXML
private ScrollPane proCarousel;
@FXML
private HBox proContainer;
@FXML
private ScrollPane tvCarousel;
@FXML
private HBox tvContainer;
@FXML
private HBox moviesContainer;
@FXML
private ScrollPane moviesCarousel;
private final StreamifyApiClient apiClient = new StreamifyApiClient();
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
loadTVShows();
}
private void loadTVShows() {
new Thread(() -> {
try {
List<Program> movies = apiClient.getPrograms();
// filtrer seulement les programs aléatoire et limiter à 10
List<Program> randomShows = movies.stream()
.filter(p -> "movie".equalsIgnoreCase(p.getTypes()))
.filter(p -> p.getActive() != 0)
.collect(Collectors.toList());
// mélanger la liste aléatoirement
Collections.shuffle(randomShows);
randomShows = randomShows.stream()
.limit(10)
.toList();
// filtrer seulement les prochainements et limiter à 10
List<Program> proShows = movies.stream()
.filter(p -> p.getActive() == 0)
.collect(Collectors.toList());
Collections.reverse(proShows);
proShows = proShows.stream()
.limit(10)
.toList();
// filtrer seulement les séries TV et limiter à 10
List<Program> tvShows = movies.stream()
.filter(p -> "tv".equalsIgnoreCase(p.getTypes()))
.filter(p -> p.getActive() != 0)
.collect(Collectors.toList());
Collections.reverse(tvShows);
tvShows = tvShows.stream()
.limit(10)
.toList();
// filtrer seulement les films MOVIES et limiter à 10
List<Program> moviesShows = movies.stream()
.filter(p -> "movie".equalsIgnoreCase(p.getTypes()))
.filter(p -> p.getActive() != 0)
.collect(Collectors.toList());
Collections.reverse(moviesShows);
moviesShows = moviesShows.stream()
.limit(10)
.toList();
List<Program> finalRandomShows = randomShows;
List<Program> finalProShows = proShows;
List<Program> finalTvShows = tvShows;
List<Program> finalMoviesShows = moviesShows;
Platform.runLater(() -> {
if (finalProShows.isEmpty()) {
proSection.setVisible(false);
proSection.setManaged(false);
} else {
proSection.setVisible(true);
proSection.setManaged(true);
shows(finalProShows, proContainer);
}
shows(finalTvShows, tvContainer);
shows(finalMoviesShows, moviesContainer);
});
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
private void shows(List<Program> finalShows, HBox container) {
container.getChildren().clear();
for (Program p : finalShows) {
VBox card = new VBox();
card.setSpacing(5);
card.setPrefWidth(150);
card.setStyle("-fx-alignment: center;");
// image
ImageView imageView = new ImageView(new Image(p.getPortraitLink(), 150, 200, true, true));
// titre
Label title = new Label(p.getTitle());
title.setMaxHeight(20);
title.setWrapText(true);
title.setTextOverrun(OverrunStyle.ELLIPSIS);
title.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-alignment: center;");
card.getChildren().addAll(imageView, title);
card.getStyleClass().add("card");
container.getChildren().add(card);
}
}
}

View file

@ -4,7 +4,16 @@ module dev.mecdu.javafx {
requires org.controlsfx.controls; requires org.controlsfx.controls;
requires org.kordamp.bootstrapfx.core; requires org.kordamp.bootstrapfx.core;
requires static lombok;
requires com.google.gson;
requires java.net.http;
requires java.desktop;
requires javafx.graphics;
opens dev.mecdu.javafx to javafx.fxml; opens dev.mecdu.javafx to javafx.fxml;
exports dev.mecdu.javafx; exports dev.mecdu.javafx;
exports dev.mecdu.javafx.ui;
opens dev.mecdu.javafx.ui to javafx.fxml;
opens dev.mecdu.javafx.model to com.google.gson;
} }

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="dev.mecdu.javafx.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="Hello!" onAction="#onHelloButtonClick"/>
</VBox>

View file

@ -0,0 +1,14 @@
.label {
-fx-max-height: 40px; /* environ 2 lignes */
-fx-text-overrun: ellipsis;
}
.card {
-fx-alignment: center;
-fx-cursor: hand;
}
.card:hover {
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.6), 10, 0, 0, 0);
}

View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="dev.mecdu.javafx.ui.HomeController"
style="-fx-background-color: #141414;"
prefWidth="1280" prefHeight="900">
<!-- HEADER FIXE -->
<top>
<BorderPane style="-fx-background-color: #000000; -fx-padding: 15 30 15 30;">
<left>
<Label text="STREAMIFY"
style="-fx-text-fill: #E50914; -fx-font-size: 28px; -fx-font-weight: bold;"/>
</left>
<center>
<HBox spacing="25" alignment="CENTER">
<Label text="Accueil" style="-fx-text-fill: white; -fx-font-size: 16px;"/>
<Label text="Séries" style="-fx-text-fill: white; -fx-font-size: 16px;"/>
<Label text="Films" style="-fx-text-fill: white; -fx-font-size: 16px;"/>
<Label text="Nouveautés" style="-fx-text-fill: white; -fx-font-size: 16px;"/>
<Label text="Ma liste" style="-fx-text-fill: white; -fx-font-size: 16px;"/>
</HBox>
</center>
</BorderPane>
</top>
<!-- CONTENU SCROLLABLE -->
<center>
<ScrollPane fitToWidth="true" hbarPolicy="NEVER" vbarPolicy="AS_NEEDED"
style="-fx-background: #ffffff; -fx-border-color: transparent;">
<VBox spacing="40" alignment="TOP_CENTER">
<padding>
<Insets top="20" right="40" bottom="20" left="40"/>
</padding>
<!-- Carrousel Prochainement -->
<VBox spacing="10" fx:id="proSection">
<Label text="🔜 Prochainement"
style="-fx-text-fill: black; -fx-font-size: 22px; -fx-font-weight: bold;"/>
<ScrollPane fx:id="proCarousel" hbarPolicy="ALWAYS" vbarPolicy="NEVER"
prefHeight="270" fitToHeight="false" fitToWidth="true"
style="-fx-background-color: transparent;">
<HBox fx:id="proContainer" spacing="10" style="-fx-padding: 10;"/>
</ScrollPane>
</VBox>
<!-- Carrousel Séries -->
<VBox spacing="10" fx:id="tvSection">
<Label text="📺 Séries récentes"
style="-fx-text-fill: black; -fx-font-size: 22px; -fx-font-weight: bold;"/>
<ScrollPane fx:id="tvCarousel" hbarPolicy="ALWAYS" vbarPolicy="NEVER"
prefHeight="270" fitToHeight="false" fitToWidth="true"
style="-fx-background-color: transparent;">
<HBox fx:id="tvContainer" spacing="10" style="-fx-padding: 10;"/>
</ScrollPane>
</VBox>
<!-- Carrousel Films -->
<VBox spacing="10" fx:id="moviesSection">
<Label text="🎥 Derniers films"
style="-fx-text-fill: black; -fx-font-size: 22px; -fx-font-weight: bold;"/>
<ScrollPane fx:id="moviesCarousel" hbarPolicy="ALWAYS" vbarPolicy="NEVER"
prefHeight="270" fitToHeight="false" fitToWidth="true"
style="-fx-background-color: transparent;">
<HBox fx:id="moviesContainer" spacing="10" style="-fx-padding: 10;"/>
</ScrollPane>
</VBox>
</VBox>
</ScrollPane>
</center>
<!-- FOOTER -->
<bottom>
<HBox alignment="CENTER" style="-fx-background-color: #000000; -fx-padding: 15;">
<Label text="© 2025 Streamify — Inspiré par Netflix"
style="-fx-text-fill: #888; -fx-font-size: 12px;"/>
</HBox>
</bottom>
</BorderPane>

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB