Implement download status for films

This commit is contained in:
Reinout Meliesie 2026-01-29 13:42:01 +01:00
commit b8cb3b1673
Signed by: zedfrigg
GPG key ID: 3AFCC06481308BC6
5 changed files with 95 additions and 4 deletions

View file

@ -72,6 +72,15 @@ impl DataManager {
.set_film_watched_status(uuid, watched) .set_film_watched_status(uuid, watched)
.await .await
} }
pub async fn set_film_downloaded_status(
uuid: &str,
downloaded: bool,
) -> Result<(), DataManagerError> {
sqlite_manager!()
.set_film_downloaded_status(uuid, downloaded)
.await
}
} }

View file

@ -30,10 +30,13 @@ impl SqliteManager {
connection connection
.prepare( .prepare(
" "
select films.uuid, films.name, films.original_name, films.release_date, films.runtime_minutes, select films.uuid, films.name, films.original_name, films.release_date,
coalesce(watched_media.watched, 0) as watched films.runtime_minutes,
from films left join watched_media coalesce(watched_media.watched, 0) as watched,
on films.uuid is watched_media.media_uuid 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(())? .query(())?
@ -92,6 +95,32 @@ impl SqliteManager {
.await .await
.map_err(convert_error) .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 { impl Debug for SqliteManager {
@ -141,6 +170,7 @@ fn row_to_film_overview(row: &Row) -> rusqlite::Result<FilmOverview> {
let release_date = row.get("release_date")?; let release_date = row.get("release_date")?;
let runtime = row.get("runtime_minutes")?; let runtime = row.get("runtime_minutes")?;
let watched = row.get("watched")?; let watched = row.get("watched")?;
let downloaded = row.get("downloaded")?;
Ok(FilmOverview { Ok(FilmOverview {
uuid, uuid,
@ -149,6 +179,7 @@ fn row_to_film_overview(row: &Row) -> rusqlite::Result<FilmOverview> {
release_date, release_date,
runtime, runtime,
watched, watched,
downloaded,
}) })
} }

View file

@ -16,17 +16,21 @@ pub struct FilmDetails {
#[derive(Debug)] #[derive(Debug)]
pub enum FilmDetailsInput { pub enum FilmDetailsInput {
ToggleWatchedStatus, ToggleWatchedStatus,
ToggleDownloadedStatus,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum FilmDetailsOutput { pub enum FilmDetailsOutput {
WatchedStatusChanged(bool), WatchedStatusChanged(bool),
DownloadedStatusChanged(bool),
} }
#[derive(Debug)] #[derive(Debug)]
pub enum FilmDetailsCmdOutput { pub enum FilmDetailsCmdOutput {
WatchedStatusPersistSucceeded, WatchedStatusPersistSucceeded,
WatchedStatusPersistFailed(DataManagerError), WatchedStatusPersistFailed(DataManagerError),
DownloadedStatusPersistSucceeded,
DownloadedStatusPersistFailed(DataManagerError),
} }
#[component(pub)] #[component(pub)]
@ -54,13 +58,23 @@ impl Component for FilmDetails {
}, },
gtk4::Box { gtk4::Box {
set_orientation: Orientation::Horizontal,
set_align: Align::Center, set_align: Align::Center,
set_spacing: 20,
ToggleButton { ToggleButton {
#[watch] #[watch]
set_label: if model.film_overview.watched { "Watched" } else { "Watch" }, set_label: if model.film_overview.watched { "Watched" } else { "Watch" },
set_active: model.film_overview.watched, set_active: model.film_overview.watched,
connect_toggled => FilmDetailsInput::ToggleWatchedStatus, 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 { 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) => { FilmDetailsCmdOutput::WatchedStatusPersistFailed(error) => {
println!("Watched status persist failed: {error:?}"); println!("Watched status persist failed: {error:?}");
} }
FilmDetailsCmdOutput::DownloadedStatusPersistSucceeded => {}
FilmDetailsCmdOutput::DownloadedStatusPersistFailed(error) => {
println!("Downloaded status persist failed: {error:?}");
}
} }
} }
} }

View file

@ -40,6 +40,7 @@ pub enum FilmGridItemInput {
ItemClicked, ItemClicked,
DetailsClosed, DetailsClosed,
WatchedStatusChanged(bool), WatchedStatusChanged(bool),
DownloadedStatusChanged(bool),
SetVisible(bool), SetVisible(bool),
} }
@ -127,6 +128,13 @@ impl FactoryComponent for FilmGridItem {
set_visible: self.film.watched, set_visible: self.film.watched,
set_icon_name: Some("eye-outline-filled-symbolic"), 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 => { FilmGridItemInput::ItemClicked => {
let details_controller = FilmDetails::builder() let details_controller = FilmDetails::builder()
.launch(self.film.clone()) .launch(self.film.clone())
// TODO: Replace this with Connector::forward.
.connect_receiver(clone!( .connect_receiver(clone!(
#[strong] #[strong]
sender, sender,
@ -185,6 +194,9 @@ impl FactoryComponent for FilmGridItem {
FilmDetailsOutput::WatchedStatusChanged(watched) => { FilmDetailsOutput::WatchedStatusChanged(watched) => {
sender.input(FilmGridItemInput::WatchedStatusChanged(watched)); sender.input(FilmGridItemInput::WatchedStatusChanged(watched));
} }
FilmDetailsOutput::DownloadedStatusChanged(downloaded) => {
sender.input(FilmGridItemInput::DownloadedStatusChanged(downloaded));
}
} }
)); ));
self.details = Some(details_controller); self.details = Some(details_controller);
@ -195,6 +207,9 @@ impl FactoryComponent for FilmGridItem {
FilmGridItemInput::WatchedStatusChanged(watched) => { FilmGridItemInput::WatchedStatusChanged(watched) => {
self.film.watched = watched; self.film.watched = watched;
} }
FilmGridItemInput::DownloadedStatusChanged(downloaded) => {
self.film.downloaded = downloaded;
}
FilmGridItemInput::SetVisible(visible) => { FilmGridItemInput::SetVisible(visible) => {
self.visible = visible; self.visible = visible;
} }

View file

@ -7,6 +7,7 @@ pub struct FilmOverview {
// In minutes. // In minutes.
pub runtime: u32, pub runtime: u32,
pub watched: bool, pub watched: bool,
pub downloaded: bool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]