From b8cb3b1673b20037063c3ea44fd8da7dde99a61f Mon Sep 17 00:00:00 2001 From: Reinout Meliesie Date: Thu, 29 Jan 2026 13:42:01 +0100 Subject: [PATCH] Implement download status for films --- src/persist/data_manager.rs | 9 +++++ src/persist/sqlite_manager.rs | 39 +++++++++++++++++-- .../components/media_details/film_details.rs | 35 +++++++++++++++++ .../media_grid_item/film_grid_item.rs | 15 +++++++ src/views/overview.rs | 1 + 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/persist/data_manager.rs b/src/persist/data_manager.rs index 7154b38..f746bec 100644 --- a/src/persist/data_manager.rs +++ b/src/persist/data_manager.rs @@ -72,6 +72,15 @@ impl DataManager { .set_film_watched_status(uuid, watched) .await } + + pub async fn set_film_downloaded_status( + uuid: &str, + downloaded: bool, + ) -> Result<(), DataManagerError> { + sqlite_manager!() + .set_film_downloaded_status(uuid, downloaded) + .await + } } diff --git a/src/persist/sqlite_manager.rs b/src/persist/sqlite_manager.rs index f6f5783..10549b0 100644 --- a/src/persist/sqlite_manager.rs +++ b/src/persist/sqlite_manager.rs @@ -30,10 +30,13 @@ impl SqliteManager { connection .prepare( " - select films.uuid, films.name, films.original_name, films.release_date, films.runtime_minutes, - coalesce(watched_media.watched, 0) as watched - from films left join watched_media - on films.uuid is watched_media.media_uuid + select films.uuid, films.name, films.original_name, films.release_date, + films.runtime_minutes, + coalesce(watched_media.watched, 0) as watched, + coalesce(downloaded_media.downloaded, 0) as downloaded + from films + left join watched_media on films.uuid is watched_media.media_uuid + left join downloaded_media on films.uuid is downloaded_media.media_uuid ", )? .query(())? @@ -92,6 +95,32 @@ impl SqliteManager { .await .map_err(convert_error) } + + pub async fn set_film_downloaded_status( + &self, + uuid: &str, + downloaded: bool, + ) -> Result<(), DataManagerError> { + self + .client + .conn({ + let uuid = uuid.to_string(); + move |connection| { + connection.execute( + " + insert into downloaded_media (media_uuid, downloaded) + values (:uuid, :downloaded) + on conflict (media_uuid) do + update set downloaded = :downloaded + ", + named_params! { ":uuid": uuid, ":downloaded": downloaded }, + )?; + Ok(()) + } + }) + .await + .map_err(convert_error) + } } impl Debug for SqliteManager { @@ -141,6 +170,7 @@ fn row_to_film_overview(row: &Row) -> rusqlite::Result { let release_date = row.get("release_date")?; let runtime = row.get("runtime_minutes")?; let watched = row.get("watched")?; + let downloaded = row.get("downloaded")?; Ok(FilmOverview { uuid, @@ -149,6 +179,7 @@ fn row_to_film_overview(row: &Row) -> rusqlite::Result { release_date, runtime, watched, + downloaded, }) } diff --git a/src/ui/components/media_details/film_details.rs b/src/ui/components/media_details/film_details.rs index 56e4894..0f71aad 100644 --- a/src/ui/components/media_details/film_details.rs +++ b/src/ui/components/media_details/film_details.rs @@ -16,17 +16,21 @@ pub struct FilmDetails { #[derive(Debug)] pub enum FilmDetailsInput { ToggleWatchedStatus, + ToggleDownloadedStatus, } #[derive(Debug)] pub enum FilmDetailsOutput { WatchedStatusChanged(bool), + DownloadedStatusChanged(bool), } #[derive(Debug)] pub enum FilmDetailsCmdOutput { WatchedStatusPersistSucceeded, WatchedStatusPersistFailed(DataManagerError), + DownloadedStatusPersistSucceeded, + DownloadedStatusPersistFailed(DataManagerError), } #[component(pub)] @@ -54,13 +58,23 @@ impl Component for FilmDetails { }, gtk4::Box { + set_orientation: Orientation::Horizontal, set_align: Align::Center, + set_spacing: 20, + ToggleButton { #[watch] set_label: if model.film_overview.watched { "Watched" } else { "Watch" }, set_active: model.film_overview.watched, connect_toggled => FilmDetailsInput::ToggleWatchedStatus, }, + + ToggleButton { + #[watch] + set_label: if model.film_overview.downloaded { "Downloaded" } else { "Mark downloaded" }, + set_active: model.film_overview.downloaded, + connect_toggled => FilmDetailsInput::ToggleDownloadedStatus, + }, }, gtk4::Box { @@ -111,6 +125,23 @@ impl Component for FilmDetails { } )); } + FilmDetailsInput::ToggleDownloadedStatus => { + let downloaded = !self.film_overview.downloaded; + self.film_overview.downloaded = downloaded; + sender.emit_output(FilmDetailsOutput::DownloadedStatusChanged(downloaded)); + + sender.oneshot_command(clone!( + #[strong(rename_to = uuid)] + self.film_overview.uuid, + async move { + let result = DataManager::set_film_downloaded_status(uuid.as_str(), downloaded).await; + match result { + Ok(()) => FilmDetailsCmdOutput::DownloadedStatusPersistSucceeded, + Err(error) => FilmDetailsCmdOutput::DownloadedStatusPersistFailed(error), + } + } + )); + } } } @@ -125,6 +156,10 @@ impl Component for FilmDetails { FilmDetailsCmdOutput::WatchedStatusPersistFailed(error) => { println!("Watched status persist failed: {error:?}"); } + FilmDetailsCmdOutput::DownloadedStatusPersistSucceeded => {} + FilmDetailsCmdOutput::DownloadedStatusPersistFailed(error) => { + println!("Downloaded status persist failed: {error:?}"); + } } } } diff --git a/src/ui/components/media_grid_item/film_grid_item.rs b/src/ui/components/media_grid_item/film_grid_item.rs index f98a318..7f9a470 100644 --- a/src/ui/components/media_grid_item/film_grid_item.rs +++ b/src/ui/components/media_grid_item/film_grid_item.rs @@ -40,6 +40,7 @@ pub enum FilmGridItemInput { ItemClicked, DetailsClosed, WatchedStatusChanged(bool), + DownloadedStatusChanged(bool), SetVisible(bool), } @@ -127,6 +128,13 @@ impl FactoryComponent for FilmGridItem { set_visible: self.film.watched, set_icon_name: Some("eye-outline-filled-symbolic"), }, + + #[name="downloaded"] + Image { + #[watch] + set_visible: self.film.downloaded, + set_icon_name: Some("folder-download-symbolic"), + }, }, }, } @@ -178,6 +186,7 @@ impl FactoryComponent for FilmGridItem { FilmGridItemInput::ItemClicked => { let details_controller = FilmDetails::builder() .launch(self.film.clone()) + // TODO: Replace this with Connector::forward. .connect_receiver(clone!( #[strong] sender, @@ -185,6 +194,9 @@ impl FactoryComponent for FilmGridItem { FilmDetailsOutput::WatchedStatusChanged(watched) => { sender.input(FilmGridItemInput::WatchedStatusChanged(watched)); } + FilmDetailsOutput::DownloadedStatusChanged(downloaded) => { + sender.input(FilmGridItemInput::DownloadedStatusChanged(downloaded)); + } } )); self.details = Some(details_controller); @@ -195,6 +207,9 @@ impl FactoryComponent for FilmGridItem { FilmGridItemInput::WatchedStatusChanged(watched) => { self.film.watched = watched; } + FilmGridItemInput::DownloadedStatusChanged(downloaded) => { + self.film.downloaded = downloaded; + } FilmGridItemInput::SetVisible(visible) => { self.visible = visible; } diff --git a/src/views/overview.rs b/src/views/overview.rs index 853decd..245909b 100644 --- a/src/views/overview.rs +++ b/src/views/overview.rs @@ -7,6 +7,7 @@ pub struct FilmOverview { // In minutes. pub runtime: u32, pub watched: bool, + pub downloaded: bool, } #[derive(Clone, Debug)]