Replace library and video player toasters with one app-wide toaster
This commit is contained in:
@@ -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);
|
||||
|
@@ -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))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -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 = >k::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_content = >k::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 = >k::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 = >k::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 = >k::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 = >k::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));
|
||||
|
@@ -9,9 +9,7 @@
|
||||
}
|
||||
|
||||
.video-player {
|
||||
& > overlay {
|
||||
background-color: black;
|
||||
}
|
||||
background-color: black;
|
||||
|
||||
.video-player-controls {
|
||||
.scrubber-position-label {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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 = >k::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 = >k::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 = >k::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 = >k::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 = >k::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 = >k::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 = >k::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();
|
||||
|
||||
|
Reference in New Issue
Block a user