Replace library and video player toasters with one app-wide toaster

This commit is contained in:
Avery
2024-02-15 19:20:36 -05:00
parent 48ec874309
commit b8cb9b9ae6
6 changed files with 151 additions and 164 deletions

View File

@@ -3,6 +3,7 @@ use gtk::glib;
use jellyfin_api::types::BaseItemDto;
use relm4::{
actions::{AccelsPlus, RelmAction, RelmActionGroup},
adw::Toast,
prelude::*,
MessageBroker,
};
@@ -90,6 +91,7 @@ pub enum AppInput {
SetThemeDark(bool),
PagePopped(Option<String>),
ShowPreferences,
Toast(String),
}
#[relm4::component(pub)]
@@ -122,18 +124,23 @@ impl Component for App {
#[wrap(Some)]
set_help_overlay = &keyboard_shortcuts(),
#[name = "navigation"]
#[wrap(Some)]
set_content = &adw::NavigationView {
add = model.servers.widget() {
set_tag: Some(&AppPage::Servers.to_string()),
},
add = model.account_list.widget() {
set_tag: Some(&AppPage::Accounts.to_string()),
},
connect_popped[sender] => move |_, page| {
sender.input(AppInput::PagePopped(page.tag().map(|s| s.to_string())));
#[name = "toaster"]
#[wrap(Some)]
set_content = &adw::ToastOverlay {
#[name = "navigation"]
#[wrap(Some)]
set_child = &adw::NavigationView {
add = model.servers.widget() {
set_tag: Some(&AppPage::Servers.to_string()),
},
add = model.account_list.widget() {
set_tag: Some(&AppPage::Accounts.to_string()),
},
connect_popped[sender] => move |_, page| {
sender.input(AppInput::PagePopped(page.tag().map(|s| s.to_string())));
},
},
},
}
@@ -344,6 +351,11 @@ impl Component for App {
.detach(),
);
}
AppInput::Toast(toast) => {
widgets
.toaster
.add_toast(Toast::builder().title(toast).timeout(3).build());
}
}
self.update_view(widgets, sender);

View File

@@ -16,8 +16,6 @@ use crate::{
utils::{item_name::ItemName, playable::get_next_playable_media},
};
use super::{LibraryInput, LIBRARY_BROKER};
#[derive(Clone, Copy)]
pub enum MediaTileDisplay {
Cover,
@@ -238,7 +236,7 @@ impl AsyncComponent for MediaTile {
if let Some(name) = self.media.name.as_ref() {
message += &format!(" for {name}");
}
LIBRARY_BROKER.send(LibraryInput::Toast(message))
APP_BROKER.send(AppInput::Toast(message))
}
};
}

View File

@@ -80,7 +80,6 @@ pub enum LibraryInput {
Shown,
Hidden,
ViewStackChildVisible(String),
Toast(String),
SearchChanged(String),
SearchingChanged(bool),
ShowSearch,
@@ -158,99 +157,95 @@ impl Component for Library {
},
},
#[name = "toaster"]
#[wrap(Some)]
set_content = &adw::ToastOverlay {
#[wrap(Some)]
set_child = &gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_content = &gtk::Box {
set_orientation: gtk::Orientation::Vertical,
#[transition = "Crossfade"]
append = match model.state {
LibraryState::Loading => {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
#[transition = "Crossfade"]
append = match model.state {
LibraryState::Loading => {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
adw::Clamp {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
set_vexpand: true,
set_halign: gtk::Align::Center,
set_valign: gtk::Align::Center,
set_spacing: 20,
adw::Clamp {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
set_vexpand: true,
set_halign: gtk::Align::Center,
set_valign: gtk::Align::Center,
set_spacing: 20,
gtk::Spinner {
set_spinning: true,
set_size_request: (64, 64),
},
gtk::Spinner {
set_spinning: true,
set_size_request: (64, 64),
},
gtk::Label {
set_label: tr!("library-loading"),
add_css_class: "title-2",
},
gtk::Label {
set_label: tr!("library-loading"),
add_css_class: "title-2",
},
}
}
}
}
LibraryState::Ready => {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
#[name = "view_stack"]
adw::ViewStack {
set_valign: gtk::Align::Fill,
add_named: (model.search_results.widget(), Some("search")),
connect_visible_child_notify[sender] => move |stack| {
if let Some(name) = stack.visible_child_name() {
sender.input(LibraryInput::ViewStackChildVisible(name.into()));
}
}
}
}
LibraryState::Ready => {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
#[name = "view_stack"]
adw::ViewStack {
set_valign: gtk::Align::Fill,
add_named: (model.search_results.widget(), Some("search")),
connect_visible_child_notify[sender] => move |stack| {
if let Some(name) = stack.visible_child_name() {
sender.input(LibraryInput::ViewStackChildVisible(name.into()));
}
},
},
},
#[name = "view_switcher_bar"]
adw::ViewSwitcherBar {
set_stack: Some(&view_stack),
#[name = "view_switcher_bar"]
adw::ViewSwitcherBar {
set_stack: Some(&view_stack),
},
}
}
LibraryState::Offline => {
adw::StatusPage {
set_title: tr!("library-offline.title"),
set_description: Some(tr!("library-offline.description")),
set_icon_name: Some("warning"),
#[wrap(Some)]
set_child = &gtk::Button {
set_label: tr!("library-status-refresh-button"),
set_halign: gtk::Align::Center,
set_css_classes: &["pill", "suggested-action"],
connect_clicked[sender] => move |_| {
sender.input(LibraryInput::Refresh);
},
}
}
LibraryState::Offline => {
adw::StatusPage {
set_title: tr!("library-offline.title"),
set_description: Some(tr!("library-offline.description")),
}
LibraryState::Error => {
adw::StatusPage {
set_title: tr!("library-error.title"),
set_description: Some(tr!("library-error.description")),
set_icon_name: Some("warning"),
#[wrap(Some)]
set_child = &gtk::Button {
set_label: tr!("library-status-refresh-button"),
set_halign: gtk::Align::Center,
set_css_classes: &["pill", "suggested-action"],
connect_clicked[sender] => move |_| {
sender.input(LibraryInput::Refresh);
},
}
set_icon_name: Some("warning"),
#[wrap(Some)]
set_child = &gtk::Button {
set_label: tr!("library-status-refresh-button"),
set_halign: gtk::Align::Center,
set_css_classes: &["pill", "suggested-action"],
connect_clicked[sender] => move |_| {
sender.input(LibraryInput::Refresh);
},
}
}
LibraryState::Error => {
adw::StatusPage {
set_title: tr!("library-error.title"),
set_description: Some(tr!("library-error.description")),
set_icon_name: Some("warning"),
#[wrap(Some)]
set_child = &gtk::Button {
set_label: tr!("library-status-refresh-button"),
set_halign: gtk::Align::Center,
set_css_classes: &["pill", "suggested-action"],
connect_clicked[sender] => move |_| {
sender.input(LibraryInput::Refresh);
},
}
}
}
},
}
},
},
},
@@ -410,10 +405,6 @@ impl Component for Library {
self.searching.set_value(false);
}
}
LibraryInput::Toast(message) => {
let toast = adw::Toast::new(&message);
widgets.toaster.add_toast(toast);
}
LibraryInput::SearchChanged(search_text) => {
self.search_results
.emit(SearchResultsInput::SearchChanged(search_text));

View File

@@ -9,9 +9,7 @@
}
.video-player {
& > overlay {
background-color: black;
}
background-color: black;
.video-player-controls {
.scrubber-position-label {

View File

@@ -10,13 +10,11 @@ use relm4::{
use tracing::warn;
use crate::{
app::{AppInput, APP_BROKER},
jellyfin_api::api_client::ApiClient,
tr,
utils::message_broker::ResettableMessageBroker,
video_player::{
backends::{SubtitleTrack, VideoPlayerBackend},
VideoPlayerInput, VIDEO_PLAYER_BROKER,
},
video_player::backends::{SubtitleTrack, VideoPlayerBackend},
};
pub static SUBTITLES_BROKER: ResettableMessageBroker<SubtitlesInput> =
@@ -183,9 +181,7 @@ impl Component for Subtitles {
}
SubtitlesInput::ToggleSubtitles => 'msg_block: {
if !self.subtitles_available {
VIDEO_PLAYER_BROKER.send(VideoPlayerInput::Toast(
tr!("vp-no-subtitles-available").into(),
));
APP_BROKER.send(AppInput::Toast(tr!("vp-no-subtitles-available").into()));
break 'msg_block;
}

View File

@@ -80,7 +80,6 @@ pub struct VideoPlayer {
#[derive(Debug)]
pub enum VideoPlayerInput {
ConfigUpdated(VideoPlayerConfig),
Toast(String),
PlayVideo(Arc<ApiClient>, Box<BaseItemDto>),
SetShowControls { show: bool, locked: bool },
ToggleControls,
@@ -133,63 +132,60 @@ impl Component for VideoPlayer {
.unwrap_or(tr!("app-name").to_string()),
#[wrap(Some)]
#[name = "toaster"]
set_child = &adw::ToastOverlay {
set_child = &gtk::Overlay {
add_css_class: "video-player",
gtk::Overlay {
#[local_ref]
video_player -> gtk::Widget {
#[watch]
set_visible: !matches!(model.player_state, PlayerState::Loading),
#[local_ref]
video_player -> gtk::Widget {
#[watch]
set_visible: !matches!(model.player_state, PlayerState::Loading),
add_controller = gtk::GestureClick {
connect_released[sender] => move |_, n_press, _, _| {
sender.input(VideoPlayerInput::MouseClick(n_press));
},
add_controller = gtk::GestureClick {
connect_released[sender] => move |_, n_press, _, _| {
sender.input(VideoPlayerInput::MouseClick(n_press));
},
},
#[name = "revealer"]
add_overlay = &gtk::Revealer {
#[watch]
set_visible: model.show_controls || model.revealer_reveal_child,
#[watch]
set_reveal_child: model.show_controls,
set_transition_type: gtk::RevealerTransitionType::Crossfade,
set_valign: gtk::Align::Start,
#[wrap(Some)]
set_child = &adw::HeaderBar {
add_css_class: "osd",
},
},
#[name = "spinner"]
add_overlay = &gtk::Spinner {
#[watch]
set_visible: matches!(model.player_state, PlayerState::Loading | PlayerState::Buffering),
set_spinning: true,
set_halign: gtk::Align::Center,
set_valign: gtk::Align::Center,
set_width_request: 48,
set_height_request: 48,
},
add_overlay = &gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_halign: gtk::Align::Fill,
set_valign: gtk::Align::End,
set_margin_start: 24,
set_margin_end: 24,
set_margin_bottom: 24,
append = model.skip_intro.widget(),
append = model.controls.widget(),
},
add_overlay: model.next_up.widget(),
},
#[name = "revealer"]
add_overlay = &gtk::Revealer {
#[watch]
set_visible: model.show_controls || model.revealer_reveal_child,
#[watch]
set_reveal_child: model.show_controls,
set_transition_type: gtk::RevealerTransitionType::Crossfade,
set_valign: gtk::Align::Start,
#[wrap(Some)]
set_child = &adw::HeaderBar {
add_css_class: "osd",
},
},
#[name = "spinner"]
add_overlay = &gtk::Spinner {
#[watch]
set_visible: matches!(model.player_state, PlayerState::Loading | PlayerState::Buffering),
set_spinning: true,
set_halign: gtk::Align::Center,
set_valign: gtk::Align::Center,
set_width_request: 48,
set_height_request: 48,
},
add_overlay = &gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_halign: gtk::Align::Fill,
set_valign: gtk::Align::End,
set_margin_start: 24,
set_margin_end: 24,
set_margin_bottom: 24,
append = model.skip_intro.widget(),
append = model.controls.widget(),
},
add_overlay: model.next_up.widget(),
},
connect_hiding[sender] => move |_| {
@@ -355,10 +351,6 @@ impl Component for VideoPlayer {
VideoPlayerInput::ConfigUpdated(video_player_config) => {
self.configure_player(&video_player_config);
}
VideoPlayerInput::Toast(message) => {
let toast = adw::Toast::new(&message);
widgets.toaster.add_toast(toast);
}
VideoPlayerInput::PlayVideo(api_client, item) => {
self.inhibit_cookie = InhibitCookie::new().ok();