feat: Added iced_video_player
Some checks failed
build / checks-matrix (push) Has been cancelled
build / codecov (push) Has been cancelled
docs / docs (push) Has been cancelled
build / checks-build (push) Has been cancelled

This commit is contained in:
uttarayan21
2025-11-20 21:59:47 +05:30
parent f41625e0ed
commit b1cfc19b96
24 changed files with 8109 additions and 15 deletions

View File

@@ -8,8 +8,10 @@ api = { version = "0.1.0", path = "../api" }
blurhash = "0.2.3"
bytes = "1.11.0"
gpui_util = "0.2.2"
iced = { git = "https://github.com/iced-rs/iced", features = ["advanced", "canvas", "image", "sipper", "tokio"] }
iced = { workspace = true }
iced_video_player = { workspace = true }
reqwest = "0.12.24"
tap = "1.0.1"
tracing = "0.1.41"
url = "2.5.7"
uuid = "1.18.1"

View File

@@ -1,6 +1,5 @@
use std::sync::{Arc, LazyLock, atomic::AtomicBool};
use iced::{Element, Length, advanced::Widget, widget::Image};
use iced::{Element, Length, advanced::Widget};
use std::sync::Arc;
use crate::shared_string::SharedString;

View File

@@ -1,5 +1,7 @@
mod shared_string;
use iced_video_player::{Video, VideoPlayer};
use shared_string::SharedString;
use std::sync::Arc;
mod blur_hash;
use blur_hash::BlurHash;
@@ -103,6 +105,13 @@ struct State {
messages: Vec<String>,
history: Vec<Option<uuid::Uuid>>,
query: Option<String>,
screen: Screen,
// Login form state
username_input: String,
password_input: String,
is_authenticated: bool,
// Video
video: Option<Arc<Video>>,
}
impl State {
@@ -115,6 +124,11 @@ impl State {
messages: Vec::new(),
history: Vec::new(),
query: None,
screen: Screen::Home,
username_input: String::new(),
password_input: String::new(),
is_authenticated: false,
video: None,
}
}
}
@@ -122,6 +136,7 @@ impl State {
#[derive(Debug, Clone)]
pub enum Message {
OpenSettings,
CloseSettings,
Refresh,
Search,
SearchQueryChanged(String),
@@ -131,12 +146,83 @@ pub enum Message {
SetToken(String),
Back,
Home,
OpenVideo(url::Url),
// Login-related messages
UsernameChanged(String),
PasswordChanged(String),
Login,
LoginSuccess(String),
Logout,
}
fn update(state: &mut State, message: Message) -> Task<Message> {
match message {
Message::OpenSettings => {
// Setting place holder
state.screen = Screen::Settings;
Task::none()
}
Message::CloseSettings => {
state.screen = Screen::Home;
Task::none()
}
Message::UsernameChanged(username) => {
state.username_input = username;
Task::none()
}
Message::PasswordChanged(password) => {
state.password_input = password;
Task::none()
}
Message::Login => {
let username = state.username_input.clone();
let password = state.password_input.clone();
// Update the client config with the new credentials
let mut config = (*state.jellyfin_client.config).clone();
config.username = username;
config.password = password;
Task::perform(
async move {
let mut client = api::JellyfinClient::new(config);
client.authenticate().await
},
|result| match result {
Ok(auth_result) => {
if let Some(token) = auth_result.access_token {
Message::LoginSuccess(token)
} else {
Message::Error("Authentication failed: No token received".to_string())
}
}
Err(e) => Message::Error(format!("Login failed: {}", e)),
},
)
}
Message::LoginSuccess(token) => {
state.jellyfin_client.set_token(token.clone());
state.is_authenticated = true;
state.password_input.clear();
state.messages.push("Login successful!".to_string());
state.screen = Screen::Home;
// Save token and refresh items
let client = state.jellyfin_client.clone();
Task::perform(
async move {
let _ = client.save_token(".session").await;
},
|_| Message::Refresh,
)
}
Message::Logout => {
state.is_authenticated = false;
state.jellyfin_client.set_token("");
state.cache = ItemCache::default();
state.current = None;
state.username_input.clear();
state.password_input.clear();
state.messages.push("Logged out successfully".to_string());
Task::none()
}
Message::OpenItem(id) => {
@@ -187,6 +273,7 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
Message::SetToken(token) => {
tracing::info!("Authenticated with token: {}", token);
state.jellyfin_client.set_token(token);
state.is_authenticated = true;
Task::none()
}
Message::Back => {
@@ -214,14 +301,33 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
}
})
}
Message::OpenVideo(url) => {
state.video = Video::new(&url).ok().map(Arc::new);
Task::none()
}
}
}
fn view(state: &State) -> Element<'_, Message> {
match state.screen {
Screen::Settings => settings(state),
Screen::Home | _ => home(state),
}
}
fn home(state: &State) -> Element<'_, Message> {
column([header(state), body(state), footer(state)]).into()
}
fn body(state: &State) -> Element<'_, Message> {
if let Some(video) = &state.video {
return container(VideoPlayer::new(video))
.width(Length::Fill)
.height(Length::Fill)
.align_x(Alignment::Center)
.align_y(Alignment::Center)
.into();
}
scrollable(
container(
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
@@ -255,14 +361,20 @@ fn header(state: &State) -> Element<'_, Message> {
.style(container::rounded_box)
.into(),
search(state),
container(row([button("Refresh").on_press(Message::Refresh).into()]).spacing(10))
.padding(10)
.width(Length::Fill)
.height(Length::Fill)
.align_x(Alignment::End)
.align_y(Alignment::Center)
.style(container::rounded_box)
.into(),
container(
row([
button("Refresh").on_press(Message::Refresh).into(),
button("Settings").on_press(Message::OpenSettings).into(),
])
.spacing(10),
)
.padding(10)
.width(Length::Fill)
.height(Length::Fill)
.align_x(Alignment::End)
.align_y(Alignment::Center)
.style(container::rounded_box)
.into(),
])
.align_y(Alignment::Center)
.width(Length::Fill)
@@ -304,6 +416,123 @@ fn footer(state: &State) -> Element<'_, Message> {
.into()
}
fn settings(state: &State) -> Element<'_, Message> {
let content = if state.is_authenticated {
// Authenticated view - show user info and logout
column([
Text::new("Settings").size(32).into(),
container(
column([
Text::new("Account").size(24).into(),
Text::new("Server URL").size(14).into(),
Text::new(state.jellyfin_client.config.server_url.as_str())
.size(12)
.into(),
container(Text::new("Status: Logged In").size(14))
.padding(10)
.width(Length::Fill)
.into(),
container(
row([
Button::new(Text::new("Logout"))
.padding(10)
.on_press(Message::Logout)
.into(),
Button::new(Text::new("Close"))
.padding(10)
.on_press(Message::CloseSettings)
.into(),
])
.spacing(10),
)
.padding(10)
.width(Length::Fill)
.into(),
])
.spacing(10)
.max_width(400)
.align_x(Alignment::Center),
)
.padding(20)
.width(Length::Fill)
.align_x(Alignment::Center)
.style(container::rounded_box)
.into(),
])
.spacing(20)
.padding(50)
.align_x(Alignment::Center)
} else {
// Not authenticated view - show login form
column([
Text::new("Settings").size(32).into(),
container(
column([
Text::new("Login to Jellyfin").size(24).into(),
Text::new("Server URL").size(14).into(),
Text::new(state.jellyfin_client.config.server_url.as_str())
.size(12)
.into(),
container(
TextInput::new("Username", &state.username_input)
.padding(10)
.size(16)
.on_input(Message::UsernameChanged),
)
.padding(10)
.width(Length::Fill)
.into(),
container(
TextInput::new("Password", &state.password_input)
.padding(10)
.size(16)
.secure(true)
.on_input(Message::PasswordChanged)
.on_submit(Message::Login),
)
.padding(10)
.width(Length::Fill)
.into(),
container(
row([
Button::new(Text::new("Login"))
.padding(10)
.on_press(Message::Login)
.into(),
Button::new(Text::new("Cancel"))
.padding(10)
.on_press(Message::CloseSettings)
.into(),
])
.spacing(10),
)
.padding(10)
.width(Length::Fill)
.into(),
])
.spacing(10)
.max_width(400)
.align_x(Alignment::Center),
)
.padding(20)
.width(Length::Fill)
.align_x(Alignment::Center)
.style(container::rounded_box)
.into(),
])
.spacing(20)
.padding(50)
.align_x(Alignment::Center)
};
container(content)
.width(Length::Fill)
.height(Length::Fill)
.align_x(Alignment::Center)
.align_y(Alignment::Center)
.into()
}
fn card(item: &Item) -> Element<'_, Message> {
let name = item
.name
@@ -341,6 +570,8 @@ fn card(item: &Item) -> Element<'_, Message> {
.into()
}
// fn video(url: &str
fn init(config: impl Fn() -> api::JellyfinConfig + 'static) -> impl Fn() -> (State, Task<Message>) {
move || {
let mut jellyfin = api::JellyfinClient::new(config());