Compare commits
3 Commits
c8c371230f
...
8aa698e65e
| Author | SHA1 | Date | |
|---|---|---|---|
| 8aa698e65e | |||
| 542679e386 | |||
| 599d91700e |
652
Cargo.lock
generated
652
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -34,10 +34,10 @@ impl JellyfinClient {
|
|||||||
username: impl AsRef<str>,
|
username: impl AsRef<str>,
|
||||||
password: impl AsRef<str>,
|
password: impl AsRef<str>,
|
||||||
config: JellyfinConfig,
|
config: JellyfinConfig,
|
||||||
) -> Result<(Self, jellyfin::AuthenticationResult)> {
|
) -> Result<Self> {
|
||||||
let url = format!("{}/Users/AuthenticateByName", config.server_url);
|
let url = format!("{}/Users/AuthenticateByName", config.server_url);
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let auth_result = client
|
let token = client
|
||||||
.post(url)
|
.post(url)
|
||||||
.json(&jellyfin::AuthenticateUserByName {
|
.json(&jellyfin::AuthenticateUserByName {
|
||||||
username: Some(username.as_ref().to_string()),
|
username: Some(username.as_ref().to_string()),
|
||||||
@@ -47,14 +47,10 @@ impl JellyfinClient {
|
|||||||
.await?
|
.await?
|
||||||
.error_for_status()?
|
.error_for_status()?
|
||||||
.json::<jellyfin::AuthenticationResult>()
|
.json::<jellyfin::AuthenticationResult>()
|
||||||
.await?;
|
.await?
|
||||||
|
|
||||||
let token = auth_result
|
|
||||||
.access_token
|
.access_token
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| std::io::Error::other("No field access_token in auth response"))?;
|
.ok_or_else(|| std::io::Error::other("No field access_token in auth response"))?;
|
||||||
|
Self::pre_authenticated(token, config)
|
||||||
Ok((Self::pre_authenticated(token, config)?, auth_result))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pre_authenticated(token: impl AsRef<str>, config: JellyfinConfig) -> Result<Self> {
|
pub fn pre_authenticated(token: impl AsRef<str>, config: JellyfinConfig) -> Result<Self> {
|
||||||
@@ -84,10 +80,6 @@ impl JellyfinClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn access_token(&self) -> Option<&str> {
|
|
||||||
self.access_token.as_deref().map(|s| &*s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn save_token(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
pub async fn save_token(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
||||||
if let Some(token) = &self.access_token {
|
if let Some(token) = &self.access_token {
|
||||||
tokio::fs::write(path, &**token).await
|
tokio::fs::write(path, &**token).await
|
||||||
@@ -112,19 +104,6 @@ impl JellyfinClient {
|
|||||||
self.access_token = Some(token.as_ref().into());
|
self.access_token = Some(token.as_ref().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn me(&self) -> Result<jellyfin::UserDto> {
|
|
||||||
let uri = "Users/Me";
|
|
||||||
let text = self
|
|
||||||
.request_builder(reqwest::Method::GET, uri)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?
|
|
||||||
.text()
|
|
||||||
.await?;
|
|
||||||
let out: jellyfin::UserDto = serde_json::from_str(&text)?;
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_builder(
|
pub fn request_builder(
|
||||||
&self,
|
&self,
|
||||||
method: reqwest::Method,
|
method: reqwest::Method,
|
||||||
|
|||||||
24
flake.lock
generated
24
flake.lock
generated
@@ -3,11 +3,11 @@
|
|||||||
"advisory-db": {
|
"advisory-db": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1766435619,
|
"lastModified": 1768679419,
|
||||||
"narHash": "sha256-3A5Z5K28YB45REOHMWtyQ24cEUXW76MOtbT6abPrARE=",
|
"narHash": "sha256-l9rM4lXBeS2mIAJsJjVfl0UABx3S3zg5tul7bv+bn50=",
|
||||||
"owner": "rustsec",
|
"owner": "rustsec",
|
||||||
"repo": "advisory-db",
|
"repo": "advisory-db",
|
||||||
"rev": "a98dbc80b16730a64c612c6ab5d5fecb4ebb79ba",
|
"rev": "c700e1cd023ca87343cbd9217d50d47023e9adc7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -18,11 +18,11 @@
|
|||||||
},
|
},
|
||||||
"crane": {
|
"crane": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1766194365,
|
"lastModified": 1768873933,
|
||||||
"narHash": "sha256-4AFsUZ0kl6MXSm4BaQgItD0VGlEKR3iq7gIaL7TjBvc=",
|
"narHash": "sha256-CfyzdaeLNGkyAHp3kT5vjvXhA1pVVK7nyDziYxCPsNk=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "7d8ec2c71771937ab99790b45e6d9b93d15d9379",
|
"rev": "0bda7e7d005ccb5522a76d11ccfbf562b71953ca",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -106,11 +106,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1766309749,
|
"lastModified": 1768564909,
|
||||||
"narHash": "sha256-3xY8CZ4rSnQ0NqGhMKAy5vgC+2IVK0NoVEzDoOh4DA4=",
|
"narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a6531044f6d0bef691ea18d4d4ce44d0daa6e816",
|
"rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -138,11 +138,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1766371695,
|
"lastModified": 1768877311,
|
||||||
"narHash": "sha256-W7CX9vy7H2Jj3E8NI4djHyF8iHSxKpb2c/7uNQ/vGFU=",
|
"narHash": "sha256-abSDl0cNr0B+YCsIDpO1SjXD9JMxE4s8EFnhLEFVovI=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "d81285ba8199b00dc31847258cae3c655b605e8c",
|
"rev": "59e4ab96304585fde3890025fd59bd2717985cc1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ struct State {
|
|||||||
settings: settings::SettingsState,
|
settings: settings::SettingsState,
|
||||||
is_authenticated: bool,
|
is_authenticated: bool,
|
||||||
video: Option<Arc<VideoHandle<Message, Ready>>>,
|
video: Option<Arc<VideoHandle<Message, Ready>>>,
|
||||||
user: Option<api::jellyfin::UserDto>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
@@ -160,14 +159,12 @@ impl State {
|
|||||||
// password_input: String::new(),
|
// password_input: String::new(),
|
||||||
is_authenticated: false,
|
is_authenticated: false,
|
||||||
video: None,
|
video: None,
|
||||||
user: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Noop,
|
|
||||||
Settings(settings::SettingsMessage),
|
Settings(settings::SettingsMessage),
|
||||||
Refresh,
|
Refresh,
|
||||||
Search,
|
Search,
|
||||||
@@ -178,30 +175,17 @@ pub enum Message {
|
|||||||
SetToken(String),
|
SetToken(String),
|
||||||
Back,
|
Back,
|
||||||
Home,
|
Home,
|
||||||
|
// Login {
|
||||||
|
// username: String,
|
||||||
|
// password: String,
|
||||||
|
// config: api::JellyfinConfig,
|
||||||
|
// },
|
||||||
|
// LoginSuccess(String),
|
||||||
|
// LoadedClient(api::JellyfinClient, bool),
|
||||||
|
// Logout,
|
||||||
Video(video::VideoMessage),
|
Video(video::VideoMessage),
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl std::fmt::Debug for Message {
|
|
||||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
// match self {
|
|
||||||
// Message::Settings(msg) => f.debug_tuple("Settings").field(msg).finish(),
|
|
||||||
// Message::Refresh => f.write_str("Refresh"),
|
|
||||||
// Message::Search => f.write_str("Search"),
|
|
||||||
// Message::SearchQueryChanged(q) => f.debug_tuple("SearchQueryChanged").field(q).finish(),
|
|
||||||
// Message::OpenItem(id) => f.debug_tuple("OpenItem").field(id).finish(),
|
|
||||||
// Message::LoadedItem(id, items) => {
|
|
||||||
// f.debug_tuple("LoadedItem").field(id).field(items).finish()
|
|
||||||
// }
|
|
||||||
// Message::Error(e) => f.debug_tuple("Error").field(e).finish(),
|
|
||||||
// Message::SetToken(_t) => f.debug_tuple("SetToken").field(&"...").finish(), // Mask token
|
|
||||||
// Message::Back => f.write_str("Back"),
|
|
||||||
// Message::Home => f.write_str("Home"),
|
|
||||||
// Message::Video(msg) => f.debug_tuple("Video").field(msg).finish(),
|
|
||||||
// Message::Noop => f.write_str("Noop"),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn update(state: &mut State, message: Message) -> Task<Message> {
|
fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Settings(msg) => settings::update(state, msg),
|
Message::Settings(msg) => settings::update(state, msg),
|
||||||
@@ -304,40 +288,14 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Video(msg) => video::update(state, msg),
|
Message::Video(msg) => video::update(state, msg),
|
||||||
Message::Noop => Task::none(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(state: &State) -> Element<'_, Message> {
|
fn view(state: &State) -> Element<'_, Message> {
|
||||||
let content = home(state);
|
match state.screen {
|
||||||
|
Screen::Settings => settings::settings(state),
|
||||||
if matches!(state.screen, Screen::Settings) {
|
Screen::Home | _ => home(state),
|
||||||
stack![
|
|
||||||
content,
|
|
||||||
mouse_area(
|
|
||||||
container(mouse_area(settings::settings(state)).on_press(Message::Refresh))
|
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Fill)
|
|
||||||
.align_x(Alignment::Center)
|
|
||||||
.align_y(Alignment::Center)
|
|
||||||
.style(|_theme| {
|
|
||||||
container::Style {
|
|
||||||
background: Some(
|
|
||||||
iced::Color {
|
|
||||||
a: 0.3,
|
|
||||||
..iced::Color::BLACK
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
..container::Style::default()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.on_press(Message::Settings(settings::SettingsMessage::Close)),
|
|
||||||
]
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,8 +328,10 @@ fn body(state: &State) -> Element<'_, Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn header(state: &State) -> Element<'_, Message> {
|
fn header(state: &State) -> Element<'_, Message> {
|
||||||
let mut left_content = row![
|
row([
|
||||||
text(
|
container(
|
||||||
|
Button::new(
|
||||||
|
Text::new(
|
||||||
state
|
state
|
||||||
.jellyfin_client
|
.jellyfin_client
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -379,15 +339,9 @@ fn header(state: &State) -> Element<'_, Message> {
|
|||||||
.unwrap_or("No Server"),
|
.unwrap_or("No Server"),
|
||||||
)
|
)
|
||||||
.align_x(Alignment::Start),
|
.align_x(Alignment::Start),
|
||||||
];
|
)
|
||||||
|
.on_press(Message::Home),
|
||||||
if let Some(user) = &state.user {
|
)
|
||||||
left_content =
|
|
||||||
left_content.push(text(format!(" | {}", user.name.as_deref().unwrap_or("?"))));
|
|
||||||
}
|
|
||||||
|
|
||||||
row([
|
|
||||||
container(Button::new(left_content).on_press(Message::Home))
|
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
@@ -520,14 +474,8 @@ fn init() -> (State, Task<Message>) {
|
|||||||
match std::fs::read_to_string(".session") {
|
match std::fs::read_to_string(".session") {
|
||||||
Ok(token) => {
|
Ok(token) => {
|
||||||
let client = api::JellyfinClient::pre_authenticated(token.trim(), config)?;
|
let client = api::JellyfinClient::pre_authenticated(token.trim(), config)?;
|
||||||
|
|
||||||
// We need to fetch the current user to fully restore state
|
|
||||||
// let _user_id = client.user_id.unwrap_or_default(); // user_id field doesn't exist on client
|
|
||||||
|
|
||||||
// We will fetch the user info in the chain if we are authenticated
|
|
||||||
Ok((client, true))
|
Ok((client, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// No cached token, create unauthenticated client
|
// No cached token, create unauthenticated client
|
||||||
let client = api::JellyfinClient::new_with_config(config);
|
let client = api::JellyfinClient::new_with_config(config);
|
||||||
@@ -536,10 +484,12 @@ fn init() -> (State, Task<Message>) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|result: Result<_, api::JellyfinApiError>| match result {
|
|result: Result<_, api::JellyfinApiError>| match result {
|
||||||
Ok((client, is_auth)) => Message::LoadedClient(client, is_auth),
|
// Ok((client, is_authenticated)) => Message::LoadedClient(client, is_authenticated),
|
||||||
Err(e) => Message::Error(format!("Initialization failed: {}", e)),
|
Err(e) => Message::Error(format!("Initialization failed: {}", e)),
|
||||||
|
_ => Message::Error("Login Unimplemented".to_string()),
|
||||||
},
|
},
|
||||||
), // .chain(Task::done(Message::Refresh)),
|
)
|
||||||
|
.chain(Task::done(Message::Refresh)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,61 +10,20 @@ pub fn update(state: &mut State, message: SettingsMessage) -> Task<Message> {
|
|||||||
SettingsMessage::Open => {
|
SettingsMessage::Open => {
|
||||||
tracing::trace!("Opening settings");
|
tracing::trace!("Opening settings");
|
||||||
state.screen = Screen::Settings;
|
state.screen = Screen::Settings;
|
||||||
Task::none()
|
|
||||||
}
|
}
|
||||||
SettingsMessage::Close => {
|
SettingsMessage::Close => {
|
||||||
tracing::trace!("Closing settings");
|
tracing::trace!("Closing settings");
|
||||||
state.screen = Screen::Home;
|
state.screen = Screen::Home;
|
||||||
Task::none()
|
|
||||||
}
|
}
|
||||||
SettingsMessage::Select(screen) => {
|
SettingsMessage::Select(screen) => {
|
||||||
tracing::trace!("Switching settings screen to {:?}", screen);
|
tracing::trace!("Switching settings screen to {:?}", screen);
|
||||||
state.settings.screen = screen;
|
state.settings.screen = screen;
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
// LoginResult(Result<(api::JellyfinClient, api::jellyfin::AuthenticationResult), String>),
|
|
||||||
// LoadedClient(api::JellyfinClient, bool),
|
|
||||||
// UserLoaded(Result<api::jellyfin::UserDto, String>),
|
|
||||||
// ConfigSaved(Result<api::JellyfinConfig, String>),
|
|
||||||
// Logout,
|
|
||||||
SettingsMessage::User(user) => {
|
|
||||||
if let UserMessage::Add = user {
|
|
||||||
// Handle adding user / login
|
|
||||||
let username = state.settings.login_form.username.clone();
|
|
||||||
let password = state.settings.login_form.password.clone();
|
|
||||||
|
|
||||||
let mut config = api::JellyfinConfig {
|
|
||||||
server_url: "http://localhost:8096".parse().unwrap(), // Default fallback
|
|
||||||
device_id: "jello-iced".to_string(),
|
|
||||||
device_name: "Jello Iced".to_string(),
|
|
||||||
client_name: "Jello".to_string(),
|
|
||||||
version: "0.1.0".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try to use existing config if possible
|
|
||||||
if let Some(client) = &state.jellyfin_client {
|
|
||||||
config = client.config.as_ref().clone();
|
|
||||||
} else if let Ok(config_str) = std::fs::read_to_string("config.toml") {
|
|
||||||
if let Ok(loaded_config) = toml::from_str(&config_str) {
|
|
||||||
config = loaded_config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task::perform(
|
|
||||||
async move {
|
|
||||||
api::JellyfinClient::authenticate(username, password, config)
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
},
|
|
||||||
Message::LoginResult,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
state.settings.login_form.update(user);
|
|
||||||
Task::none()
|
|
||||||
}
|
}
|
||||||
|
SettingsMessage::User(user) => state.settings.login_form.update(user),
|
||||||
|
|
||||||
SettingsMessage::Server(server) => state.settings.server_form.update(server),
|
SettingsMessage::Server(server) => state.settings.server_form.update(server),
|
||||||
}
|
}
|
||||||
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty() -> Element<'static, Message> {
|
pub fn empty() -> Element<'static, Message> {
|
||||||
@@ -73,9 +32,9 @@ pub fn empty() -> Element<'static, Message> {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct SettingsState {
|
pub struct SettingsState {
|
||||||
pub login_form: LoginForm,
|
login_form: LoginForm,
|
||||||
pub server_form: ServerForm,
|
server_form: ServerForm,
|
||||||
pub screen: SettingsScreen,
|
screen: SettingsScreen,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -95,7 +54,6 @@ pub enum UserMessage {
|
|||||||
// Edit(uuid::Uuid),
|
// Edit(uuid::Uuid),
|
||||||
// Delete(uuid::Uuid),
|
// Delete(uuid::Uuid),
|
||||||
Clear,
|
Clear,
|
||||||
Error(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -132,9 +90,8 @@ pub struct UserItem {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct LoginForm {
|
pub struct LoginForm {
|
||||||
pub username: String,
|
username: String,
|
||||||
pub password: String,
|
password: String,
|
||||||
pub error: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoginForm {
|
impl LoginForm {
|
||||||
@@ -142,11 +99,9 @@ impl LoginForm {
|
|||||||
match message {
|
match message {
|
||||||
UserMessage::UsernameChanged(data) => {
|
UserMessage::UsernameChanged(data) => {
|
||||||
self.username = data;
|
self.username = data;
|
||||||
self.error = None; // Clear error on input
|
|
||||||
}
|
}
|
||||||
UserMessage::PasswordChanged(data) => {
|
UserMessage::PasswordChanged(data) => {
|
||||||
self.password = data;
|
self.password = data;
|
||||||
self.error = None; // Clear error on input
|
|
||||||
}
|
}
|
||||||
UserMessage::Add => {
|
UserMessage::Add => {
|
||||||
// Handle adding user
|
// Handle adding user
|
||||||
@@ -154,18 +109,12 @@ impl LoginForm {
|
|||||||
UserMessage::Clear => {
|
UserMessage::Clear => {
|
||||||
self.username.clear();
|
self.username.clear();
|
||||||
self.password.clear();
|
self.password.clear();
|
||||||
self.error = None;
|
|
||||||
}
|
|
||||||
UserMessage::Error(msg) => {
|
|
||||||
self.error = Some(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn view(&self, is_authenticated: bool) -> Element<'_, Message> {
|
pub fn view(&self) -> Element<'_, Message> {
|
||||||
let mut col = iced::widget::column![text("Login Form"),];
|
iced::widget::column![
|
||||||
|
text("Login Form"),
|
||||||
if !is_authenticated {
|
|
||||||
let mut inputs = iced::widget::column![
|
|
||||||
text_input("Enter Username", &self.username).on_input(|data| {
|
text_input("Enter Username", &self.username).on_input(|data| {
|
||||||
Message::Settings(SettingsMessage::User(UserMessage::UsernameChanged(data)))
|
Message::Settings(SettingsMessage::User(UserMessage::UsernameChanged(data)))
|
||||||
}),
|
}),
|
||||||
@@ -174,28 +123,16 @@ impl LoginForm {
|
|||||||
.on_input(|data| {
|
.on_input(|data| {
|
||||||
Message::Settings(SettingsMessage::User(UserMessage::PasswordChanged(data)))
|
Message::Settings(SettingsMessage::User(UserMessage::PasswordChanged(data)))
|
||||||
}),
|
}),
|
||||||
]
|
|
||||||
.spacing(10);
|
|
||||||
|
|
||||||
if let Some(err) = &self.error {
|
|
||||||
inputs = inputs.push(text(err).style(|_| text::Style {
|
|
||||||
color: Some(iced::Color::from_rgb(0.8, 0.0, 0.0)),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
col = col.push(inputs).push(
|
|
||||||
row![
|
row![
|
||||||
button(text("Login")).on_press_maybe(self.validate()),
|
button(text("Add User")).on_press_maybe(self.validate()),
|
||||||
button(text("Cancel"))
|
button(text("Cancel"))
|
||||||
.on_press(Message::Settings(SettingsMessage::User(UserMessage::Clear))),
|
.on_press(Message::Settings(SettingsMessage::User(UserMessage::Clear))),
|
||||||
]
|
]
|
||||||
.spacing(10),
|
.spacing(10),
|
||||||
);
|
]
|
||||||
} else {
|
.spacing(10)
|
||||||
col = col.push(row![button(text("Logout")).on_press(Message::Logout)].spacing(10));
|
.padding([10, 0])
|
||||||
}
|
.into()
|
||||||
|
|
||||||
col.spacing(10).padding([10, 0]).into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self) -> Option<Message> {
|
pub fn validate(&self) -> Option<Message> {
|
||||||
@@ -211,7 +148,7 @@ pub struct ServerForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ServerForm {
|
impl ServerForm {
|
||||||
pub fn update(&mut self, message: ServerMessage) -> Task<Message> {
|
pub fn update(&mut self, message: ServerMessage) {
|
||||||
match message {
|
match message {
|
||||||
ServerMessage::NameChanged(data) => {
|
ServerMessage::NameChanged(data) => {
|
||||||
self.name = data;
|
self.name = data;
|
||||||
@@ -220,36 +157,7 @@ impl ServerForm {
|
|||||||
self.url = data;
|
self.url = data;
|
||||||
}
|
}
|
||||||
ServerMessage::Add => {
|
ServerMessage::Add => {
|
||||||
// Handle adding server (saving config)
|
// Handle adding server
|
||||||
let name = self.name.clone();
|
|
||||||
let url_str = self.url.clone();
|
|
||||||
|
|
||||||
return Task::perform(
|
|
||||||
async move {
|
|
||||||
// Try to parse the URL
|
|
||||||
let url_parsed = url::Url::parse(&url_str)
|
|
||||||
.or_else(|_| url::Url::parse(&format!("http://{}", url_str)))
|
|
||||||
.map_err(|e| format!("Invalid URL: {}", e))?;
|
|
||||||
|
|
||||||
// Create new config
|
|
||||||
let config = api::JellyfinConfig {
|
|
||||||
server_url: url_parsed.to_string().parse().unwrap(),
|
|
||||||
device_id: "jello-iced".to_string(),
|
|
||||||
device_name: name,
|
|
||||||
client_name: "Jello".to_string(),
|
|
||||||
version: "0.1.0".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Save to config.toml
|
|
||||||
let toml_str = toml::to_string(&config)
|
|
||||||
.map_err(|e| format!("Failed to serialize config: {}", e))?;
|
|
||||||
std::fs::write("config.toml", toml_str)
|
|
||||||
.map_err(|e| format!("Failed to write config file: {}", e))?;
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
},
|
|
||||||
Message::ConfigSaved,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
ServerMessage::Clear => {
|
ServerMessage::Clear => {
|
||||||
self.name.clear();
|
self.name.clear();
|
||||||
@@ -257,7 +165,6 @@ impl ServerForm {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
Task::none()
|
|
||||||
}
|
}
|
||||||
pub fn view(&self) -> Element<'_, Message> {
|
pub fn view(&self) -> Element<'_, Message> {
|
||||||
iced::widget::column![
|
iced::widget::column![
|
||||||
@@ -290,15 +197,7 @@ impl ServerForm {
|
|||||||
mod screens {
|
mod screens {
|
||||||
use super::*;
|
use super::*;
|
||||||
pub fn settings(state: &State) -> Element<'_, Message> {
|
pub fn settings(state: &State) -> Element<'_, Message> {
|
||||||
container(
|
row([settings_list(state), settings_screen(state)]).into()
|
||||||
row([settings_list(state), settings_screen(state)])
|
|
||||||
.spacing(20)
|
|
||||||
.width(Length::Fixed(800.0))
|
|
||||||
.height(Length::Fixed(600.0)),
|
|
||||||
)
|
|
||||||
.padding(20)
|
|
||||||
.style(container::rounded_box)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn settings_screen(state: &State) -> Element<'_, Message> {
|
pub fn settings_screen(state: &State) -> Element<'_, Message> {
|
||||||
@@ -357,24 +256,10 @@ mod screens {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
pub fn user(state: &State) -> Element<'_, Message> {
|
pub fn user(state: &State) -> Element<'_, Message> {
|
||||||
let user_display = if let Some(user) = &state.user {
|
|
||||||
iced::widget::column![
|
|
||||||
text(format!(
|
|
||||||
"Logged in as: {}",
|
|
||||||
user.name.as_deref().unwrap_or("Unknown")
|
|
||||||
)),
|
|
||||||
// We could add an avatar here if we had image loading for it
|
|
||||||
]
|
|
||||||
.spacing(10)
|
|
||||||
} else {
|
|
||||||
iced::widget::column![].into()
|
|
||||||
};
|
|
||||||
|
|
||||||
container(
|
container(
|
||||||
Column::new()
|
Column::new()
|
||||||
.push(text("User Settings"))
|
.push(text("User Settings"))
|
||||||
.push(user_display)
|
.push(state.settings.login_form.view())
|
||||||
.push(state.settings.login_form.view(state.is_authenticated))
|
|
||||||
// .push(userlist(&state))
|
// .push(userlist(&state))
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.padding(20),
|
.padding(20),
|
||||||
@@ -389,100 +274,3 @@ pub fn center_text(content: &str) -> Element<'_, Message> {
|
|||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message::ConfigSaved(result) => {
|
|
||||||
// match result {
|
|
||||||
// Ok(config) => {
|
|
||||||
// tracing::info!("Configuration saved successfully.");
|
|
||||||
// state.messages.push("Configuration saved.".to_string());
|
|
||||||
//
|
|
||||||
// // Re-initialize client with new config
|
|
||||||
// // This invalidates the current session as the server might have changed
|
|
||||||
// state.jellyfin_client = Some(api::JellyfinClient::new_with_config(config));
|
|
||||||
// state.is_authenticated = false;
|
|
||||||
// state.user = None;
|
|
||||||
//
|
|
||||||
// // Clear session file as it likely belongs to the old server
|
|
||||||
// let _ = std::fs::remove_file(".session");
|
|
||||||
//
|
|
||||||
// // Reset cache
|
|
||||||
// state.cache = ItemCache::default();
|
|
||||||
// state.current = None;
|
|
||||||
// state.history.clear();
|
|
||||||
//
|
|
||||||
// Task::none()
|
|
||||||
// }
|
|
||||||
// Err(e) => {
|
|
||||||
// tracing::error!("Failed to save configuration: {}", e);
|
|
||||||
// state.messages.push(format!("Failed to save config: {}", e));
|
|
||||||
// Task::none()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Message::LoadedClient(client, is_auth) => {
|
|
||||||
// state.jellyfin_client = Some(client.clone());
|
|
||||||
// state.is_authenticated = is_auth;
|
|
||||||
// if is_auth {
|
|
||||||
// // Fetch user if authenticated
|
|
||||||
// Task::perform(
|
|
||||||
// async move { client.me().await.map_err(|e| e.to_string()) },
|
|
||||||
// Message::UserLoaded,
|
|
||||||
// )
|
|
||||||
// } else {
|
|
||||||
// Task::done(Message::Refresh)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Message::UserLoaded(result) => {
|
|
||||||
// match result {
|
|
||||||
// Ok(user) => {
|
|
||||||
// state.user = Some(user);
|
|
||||||
// }
|
|
||||||
// Err(e) => {
|
|
||||||
// tracing::warn!("Failed to load user profile: {}", e);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Task::done(Message::Refresh)
|
|
||||||
// }
|
|
||||||
// Message::LoginResult(result) => {
|
|
||||||
// match result {
|
|
||||||
// Ok((client, auth)) => {
|
|
||||||
// if let Some(token) = client.access_token() {
|
|
||||||
// if let Err(e) = std::fs::write(".session", token) {
|
|
||||||
// tracing::error!("Failed to save session token: {}", e);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// // Fetch user here too since authentication just succeeded
|
|
||||||
// state.jellyfin_client = Some(client.clone());
|
|
||||||
// state.is_authenticated = true;
|
|
||||||
// state.settings.login_form = settings::LoginForm::default();
|
|
||||||
//
|
|
||||||
// // We can use the auth.user if present, or fetch it.
|
|
||||||
// if let Some(user_dto) = auth.user {
|
|
||||||
// state.user = Some(user_dto);
|
|
||||||
// Task::none()
|
|
||||||
// } else {
|
|
||||||
// Task::perform(
|
|
||||||
// async move { client.me().await.map_err(|e| e.to_string()) },
|
|
||||||
// Message::UserLoaded,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Err(e) => {
|
|
||||||
// tracing::error!("Login failed: {}", e);
|
|
||||||
// state.messages.push(format!("Login failed: {}", e));
|
|
||||||
// // Pass the error to the settings/login form so it can be displayed
|
|
||||||
// state
|
|
||||||
// .settings
|
|
||||||
// .login_form
|
|
||||||
// .update(settings::UserMessage::Error(e));
|
|
||||||
// Task::none()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Message::Logout => {
|
|
||||||
// state.jellyfin_client = None;
|
|
||||||
// state.is_authenticated = false;
|
|
||||||
// state.user = None;
|
|
||||||
// let _ = std::fs::remove_file(".session");
|
|
||||||
// Task::none()
|
|
||||||
// }
|
|
||||||
|
|||||||
Reference in New Issue
Block a user