Merge pull request #1002 from GyulyVGC/welcome

Add welcome screen
This commit is contained in:
Giuliano Bellini 2025-11-09 23:50:46 +01:00 • committed by GitHub
commit 54e47a093a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 120 additions and 6 deletions

View file

@ -2,6 +2,9 @@
All Sniffnet releases with the relative changes are documented in this file.
## [UNRELEASED]
- Add animated welcome screen ([#1002](https://github.com/GyulyVGC/sniffnet/pull/1002))
## [1.4.2] - 2025-11-04
- Send remote notifications via webhook ([#991](https://github.com/GyulyVGC/sniffnet/pull/991) — fixes [#841](https://github.com/GyulyVGC/sniffnet/issues/841))
- Pause and resume packet captures ([#992](https://github.com/GyulyVGC/sniffnet/pull/992) — fixes [#551](https://github.com/GyulyVGC/sniffnet/issues/551))

Binary file not shown.

View file

@ -8,3 +8,4 @@ pub mod settings_notifications_page;
pub mod settings_style_page;
pub mod thumbnail_page;
pub mod types;
pub mod welcome_page;

View file

@ -0,0 +1,55 @@
use crate::StyleType;
use crate::gui::styles::text::TextType;
use crate::gui::types::message::Message;
use crate::utils::types::icon::Icon;
use iced::alignment::{Horizontal, Vertical};
use iced::widget::{Column, Container, Space, Text, vertical_space};
use iced::{Alignment, Font, Length};
pub fn welcome_page<'a>(font: Font, x: u8) -> Container<'a, Message, StyleType> {
let icon = match x {
0..=3 | 20.. => Text::new(""),
4 => Icon::Sniffnet1.to_text(),
5 => Icon::Sniffnet2.to_text(),
6 => Icon::Sniffnet3.to_text(),
7 => Icon::Sniffnet4.to_text(),
8..=19 => Icon::Sniffnet.to_text(),
};
let text = Text::new(match x {
0..=3 | 20.. => "",
4 => "S",
5 => "Sn",
6 => "Sni",
7 => "Snif",
8 => "Sniff",
9 => "Sniffn",
10 => "Sniffne",
11..=19 => "Sniffnet",
});
let text_type = match x {
0..=3 | 20.. => TextType::Welcome(0.0),
4 => TextType::Welcome(0.125),
5 => TextType::Welcome(0.25),
6 => TextType::Welcome(0.375),
7 => TextType::Welcome(0.5),
8 => TextType::Welcome(0.625),
9 => TextType::Welcome(0.750),
10 => TextType::Welcome(0.875),
11..=19 => TextType::Welcome(1.0),
};
let body = Column::new()
.align_x(Alignment::Center)
.push(vertical_space())
.push(icon.size(200).line_height(0.9).class(text_type))
.push(text.font(font).size(75).class(text_type))
.push(Space::with_height(Length::FillPortion(2)));
Container::new(body)
.height(Length::Fill)
.width(Length::Fill)
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
}

View file

@ -33,6 +33,7 @@ use crate::gui::pages::settings_style_page::settings_style_page;
use crate::gui::pages::thumbnail_page::thumbnail_page;
use crate::gui::pages::types::running_page::RunningPage;
use crate::gui::pages::types::settings_page::SettingsPage;
use crate::gui::pages::welcome_page::welcome_page;
use crate::gui::styles::types::custom_palette::{CustomPalette, ExtraStyles};
use crate::gui::styles::types::palette::Palette;
use crate::gui::types::conf::Conf;
@ -77,6 +78,8 @@ pub const ICON_FONT_FAMILY_NAME: &str = "Icons for Sniffnet";
pub struct Sniffer {
/// Parameters that are persistent across runs
pub conf: Conf,
/// Are we during welcome / quit animation? None if not, true if welcome, false if quit
pub welcome: Option<(bool, u8)>,
/// Capture receiver clone (to close the channel after every run), with the current capture id (to ignore pending messages from previous captures)
pub current_capture_rx: (usize, Option<Receiver<BackendTrafficMessage>>),
/// Capture data
@ -140,6 +143,7 @@ impl Sniffer {
let capture_source = CaptureSource::from_conf(&conf);
Self {
conf,
welcome: Some((true, 0)),
current_capture_rx: (0, None),
info_traffic: InfoTraffic::default(),
addresses_resolved: HashMap::new(),
@ -173,6 +177,10 @@ impl Sniffer {
fn keyboard_subscription(&self) -> Subscription<Message> {
const NO_MODIFIER: Modifiers = Modifiers::empty();
if self.welcome.is_some() {
return Subscription::none();
}
if self.thumbnail {
iced::event::listen_with(|event, _, _| match event {
Keyboard(Event::KeyPressed {
@ -231,8 +239,17 @@ impl Sniffer {
}
}
fn time_subscription() -> Subscription<Message> {
iced::time::every(Duration::from_millis(1000)).map(|_| Message::Periodic)
fn time_subscription(&self) -> Subscription<Message> {
if let Some((w, _)) = self.welcome {
let sub = iced::time::every(Duration::from_millis(100));
if w {
sub.map(|_| Message::Welcome)
} else {
sub.map(|_| Message::Quit)
}
} else {
iced::time::every(Duration::from_millis(1000)).map(|_| Message::Periodic)
}
}
fn window_subscription() -> Subscription<Message> {
@ -430,8 +447,24 @@ impl Sniffer {
}
Message::QuitWrapper => return self.quit_wrapper(),
Message::Quit => {
let _ = self.conf.clone().store();
return window::close(self.id.unwrap_or_else(Id::unique));
if self.welcome.is_none() {
self.welcome = Some((false, 13));
} else if let Some((false, x)) = self.welcome {
if x <= 2 {
let _ = self.conf.clone().store();
return window::close(self.id.unwrap_or_else(Id::unique));
}
self.welcome = Some((false, x.saturating_sub(1)));
}
}
Message::Welcome => {
if let Some((true, x)) = self.welcome {
if x >= 19 {
self.welcome = None;
} else {
self.welcome = Some((true, x + 1));
}
}
}
Message::CopyIp(ip) => {
self.timing_events.copy_ip_now(ip);
@ -595,6 +628,10 @@ impl Sniffer {
let font = style.get_extension().font;
let font_headers = style.get_extension().font_headers;
if let Some((_, x)) = self.welcome {
return welcome_page(font, x).into();
}
let header = header(self);
let body = if self.thumbnail {
@ -676,7 +713,7 @@ impl Sniffer {
Subscription::batch([
self.keyboard_subscription(),
self.mouse_subscription(),
Sniffer::time_subscription(),
self.time_subscription(),
Sniffer::window_subscription(),
])
}
@ -1895,7 +1932,8 @@ mod tests {
sniffer.update(Message::ChangeRunningPage(RunningPage::Notifications));
sniffer.update(Message::DataReprSelection(DataRepr::Bits));
// quit the app by sending a CloseRequested message
// force saving configs by quitting the app
sniffer.welcome = Some((false, 0));
sniffer.update(Message::Quit);
assert!(path.exists());

View file

@ -18,6 +18,7 @@ pub enum TextType {
Subtitle,
Danger,
Sponsor,
Welcome(f32),
}
/// Returns a formatted caption followed by subtitle, new line, tab, and desc
@ -71,6 +72,12 @@ pub fn highlight(style: &StyleType, element: TextType) -> Color {
a: 1.0,
}
}
TextType::Welcome(mut n) => {
if !(0.0..=1.0).contains(&n) {
n = 1.0;
}
Color { a: n, ..secondary }
}
TextType::Incoming => colors.secondary,
TextType::Outgoing => colors.outgoing,
TextType::Danger | TextType::Sponsor => ext.red_alert_color,

View file

@ -21,6 +21,8 @@ use crate::{Language, StyleType};
pub enum Message {
/// Run tasks to initialize the app
StartApp(Option<window::Id>),
/// Animate welcome page
Welcome,
/// Sent by the backend parsing packets; includes the capture id, new data, new hosts batched data, and whether an offline capture has finished
TickRun(usize, InfoTraffic, Vec<HostMessage>, bool),
/// Capture source selected from the picklist

View file

@ -42,6 +42,10 @@ pub enum Icon {
// Rocket,
Settings,
Sniffnet,
Sniffnet1,
Sniffnet2,
Sniffnet3,
Sniffnet4,
SortAscending,
SortDescending,
SortNeutral,
@ -90,6 +94,10 @@ impl Icon {
// Icon::Rocket => 'S',
Icon::Settings => 'a',
Icon::Sniffnet => 'A',
Icon::Sniffnet1 => '!',
Icon::Sniffnet2 => '"',
Icon::Sniffnet3 => '#',
Icon::Sniffnet4 => '$',
Icon::Star => 'g',
Icon::Warning => 'T',
Icon::Waves => 'y',