feat: Update to latest iced

This commit is contained in:
uttarayan21
2025-11-18 23:54:27 +05:30
parent a6ef6ba9c0
commit 3222c26bb6
15 changed files with 1231 additions and 4918 deletions

View File

@@ -4,6 +4,13 @@ version = "0.1.0"
edition = "2024"
[dependencies]
api = { version = "0.1.0", path = "../api" }
gpui_util = "0.2.2"
iced = { version = "0.13.1", features = ["canvas", "image", "tokio"] }
iced = { git = "https://github.com/iced-rs/iced", features = [
"advanced",
"canvas",
"image",
"tokio",
] }
tracing = "0.1.41"
uuid = "1.18.1"

View File

@@ -1,33 +1,228 @@
use std::{collections::BTreeMap, sync::Arc};
type SharedString = Arc<str>;
mod shared_string;
use shared_string::SharedString;
use iced::{Element, Task};
struct State {
loading: Option<Loading>,
current: Screen,
cache: ItemCache,
}
pub struct ItemCache {
pub items: BTreeMap<uuid::Uuid, Item>,
}
#[derive(Clone, Debug)]
pub struct Item {
pub id: SharedString,
pub name: SharedString,
pub item_type: SharedString,
pub media_type: SharedString,
}
pub enum Screen {
Home,
Settings,
Profile,
}
use iced::{Alignment, Element, Length, Task, widget::*};
use std::collections::BTreeMap;
#[derive(Debug, Clone)]
pub struct Loading {
to: Screen,
from: Screen,
}
#[derive(Default, Debug, Clone)]
pub struct ItemCache {
pub items: BTreeMap<uuid::Uuid, Item>,
}
impl ItemCache {
pub fn insert(&mut self, item: Item) {
self.items.insert(item.id, item);
}
pub fn extend<I: IntoIterator<Item = Item>>(&mut self, items: I) {
self.items
.extend(items.into_iter().map(|item| (item.id, item)));
}
}
#[derive(Clone, Debug)]
pub struct Image {
pub id: SharedString,
pub blur_hash: Option<SharedString>,
}
impl From<api::jellyfin::BaseItemDto> for Item {
fn from(dto: api::jellyfin::BaseItemDto) -> Self {
Item {
id: dto.id,
name: dto.name.map(Into::into),
picture: dto
.image_tags
.and_then(|tags| tags.get("Primary").cloned())
.map(|tag| Image {
id: tag.clone().into(),
blur_hash: dto
.image_blur_hashes
.primary
.get(&tag)
.map(|s| s.clone().into()),
}),
}
}
}
#[derive(Clone, Debug)]
pub struct Item {
pub id: uuid::Uuid,
pub name: Option<SharedString>,
pub picture: Option<Image>,
}
#[derive(Debug, Clone, Default)]
pub enum Screen {
#[default]
Home,
Settings,
Profile,
}
#[derive(Debug, Clone)]
struct State {
loading: Option<Loading>,
current: Option<uuid::Uuid>,
cache: ItemCache,
jellyfin_client: api::JellyfinClient,
}
impl State {
pub fn new(jellyfin_client: api::JellyfinClient) -> Self {
State {
loading: None,
current: None,
cache: ItemCache::default(),
jellyfin_client,
}
}
}
#[derive(Debug, Clone)]
pub enum Message {
OpenSettings,
Refresh,
OpenItem(uuid::Uuid),
LoadedItem(uuid::Uuid, Vec<Item>),
Error(String),
SetToken(String),
}
fn update(state: &mut State, message: Message) -> Task<Message> {
match message {
Message::OpenSettings => {
// Foo
Task::none()
}
Message::OpenItem(id) => {
let client = state.jellyfin_client.clone();
Task::perform(
async move {
let items: Result<Vec<Item>, api::JellyfinApiError> = client
.items(id)
.await
.map(|items| items.into_iter().map(Item::from).collect());
(id, items)
},
|(msg, items)| match items {
Err(e) => Message::Error(format!("Failed to load item: {}", e)),
Ok(items) => Message::LoadedItem(msg, items),
},
)
}
Message::LoadedItem(id, items) => {
state.cache.extend(items);
state.current = Some(id);
Task::none()
}
Message::Refresh => {
// Handle refresh logic
Task::none()
}
Message::Error(err) => {
tracing::error!("Error: {}", err);
Task::none()
}
Message::SetToken(token) => {
state.jellyfin_client.set_token(token);
Task::none()
}
}
}
fn view(state: &State) -> Element<'_, Message> {
column([header(), body(state)]).into()
}
fn body(state: &State) -> Element<'_, Message> {
container(Text::new("Home Screen"))
.align_x(Alignment::Center)
.align_y(Alignment::Center)
.height(Length::Fill)
.width(Length::Fill)
.into()
}
fn header() -> Element<'static, Message> {
row([
container(
Text::new("Jello")
.width(Length::Fill)
.align_x(Alignment::Start),
)
.padding(10)
.width(Length::Fill)
.height(Length::Fill)
.align_x(Alignment::Start)
.align_y(Alignment::Center)
.style(container::rounded_box)
.into(),
container(
row([
button("Settings").on_press(Message::OpenSettings).into(),
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(),
])
.align_y(Alignment::Center)
.width(Length::Fill)
.height(50)
.into()
}
fn card(item: &Item) -> Element<'_, Message> {
let name = item
.name
.as_ref()
.map(|s| s.as_ref())
.unwrap_or("Unnamed Item");
container(
column([
Text::new(name).size(16).into(),
iced::widget::Image::new("placeholder.png")
.width(Length::Fill)
.height(150)
.into(),
])
.spacing(10),
)
.padding(10)
.width(Length::FillPortion(5))
.height(Length::FillPortion(5))
.style(container::rounded_box)
.into()
}
fn init(config: impl Fn() -> api::JellyfinConfig + 'static) -> impl Fn() -> (State, Task<Message>) {
move || {
let mut jellyfin = api::JellyfinClient::new(config());
(
State::new(jellyfin.clone()),
Task::perform(
async move { jellyfin.authenticate_with_cached_token(".session").await },
|token| match token {
Ok(token) => Message::SetToken(token),
Err(e) => Message::Error(format!("Authentication failed: {}", e)),
},
),
)
}
}
pub fn ui(config: impl Fn() -> api::JellyfinConfig + 'static) -> iced::Result {
iced::application(init(config), update, view).run()
}

View File

@@ -0,0 +1,68 @@
use std::{borrow::Cow, sync::Arc};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SharedString(ArcCow<'static, str>);
impl From<String> for SharedString {
fn from(s: String) -> Self {
SharedString(ArcCow::Owned(Arc::from(s)))
}
}
impl<'a> iced::advanced::text::IntoFragment<'a> for SharedString {
fn into_fragment(self) -> Cow<'a, str> {
match self.0 {
ArcCow::Borrowed(b) => Cow::Borrowed(b),
ArcCow::Owned(o) => Cow::Owned(o.as_ref().to_string()),
}
}
}
impl<'a> iced::advanced::text::IntoFragment<'a> for &SharedString {
fn into_fragment(self) -> Cow<'a, str> {
match &self.0 {
ArcCow::Borrowed(b) => Cow::Borrowed(b),
ArcCow::Owned(o) => Cow::Owned(o.as_ref().to_string()),
}
}
}
impl From<&'static str> for SharedString {
fn from(s: &'static str) -> Self {
SharedString(ArcCow::Borrowed(s))
}
}
impl AsRef<str> for SharedString {
fn as_ref(&self) -> &str {
match &self.0 {
ArcCow::Borrowed(b) => b,
ArcCow::Owned(o) => o.as_ref(),
}
}
}
impl std::ops::Deref for SharedString {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ArcCow<'a, T: ?Sized> {
Borrowed(&'a T),
Owned(Arc<T>),
}
impl<'a, T> Clone for ArcCow<'a, T>
where
T: ?Sized,
{
fn clone(&self) -> Self {
match self {
ArcCow::Borrowed(b) => ArcCow::Borrowed(b),
ArcCow::Owned(o) => ArcCow::Owned(Arc::clone(o)),
}
}
}