feat: Update to latest iced
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
68
ui-iced/src/shared_string.rs
Normal file
68
ui-iced/src/shared_string.rs
Normal 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user