sniffnet 1.5.0

Application to comfortably monitor your network traffic
use iced::widget::text::LineHeight;
use iced::widget::tooltip::Position;
use iced::widget::{Column, Container, PickList, Row, Slider, Space, Text, Tooltip, button};
use iced::{Alignment, Length, Padding};

use crate::gui::components::button::{button_open_file, row_open_link_tooltip};
use crate::gui::components::tab::get_settings_tabs;
use crate::gui::pages::settings_notifications_page::settings_header;
use crate::gui::pages::types::settings_page::SettingsPage;
use crate::gui::styles::button::ButtonType;
use crate::gui::styles::container::ContainerType;
use crate::gui::styles::rule::RuleType;
use crate::gui::styles::style_constants::{FONT_SIZE_SUBTITLE, TOOLTIP_DELAY};
use crate::gui::styles::text::TextType;
use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::mmdb::types::mmdb_reader::{MmdbReader, MmdbReaders};
use crate::networking::types::ip_blacklist::IpBlacklist;
use crate::translations::translations::language_translation;
use crate::translations::translations_2::country_translation;
use crate::translations::translations_3::{
    mmdb_files_translation, params_not_editable_translation, zoom_translation,
};
use crate::translations::translations_4::share_feedback_translation;
use crate::translations::translations_5::ip_blacklist_translation;
use crate::utils::formatted_strings::get_path_termination_string;
use crate::utils::types::file_info::FileInfo;
use crate::utils::types::icon::Icon;
use crate::utils::types::web_page::WebPage;
use crate::{Language, Sniffer, StyleType};

pub fn settings_general_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
    let Settings {
        language,
        color_gradient,
        ..
    } = sniffer.conf.settings;

    let content = Column::new()
        .align_x(Alignment::Center)
        .width(Length::Fill)
        .push(settings_header(color_gradient, language))
        .push(get_settings_tabs(SettingsPage::General, language))
        .push(Space::new().height(10))
        .push(column_all_general_setting(sniffer));

    Container::new(content)
        .height(400)
        .width(800)
        .class(ContainerType::Modal)
}

fn column_all_general_setting(sniffer: &Sniffer) -> Column<'_, Message, StyleType> {
    let Settings {
        language,
        scale_factor,
        ref mmdb_country,
        ref mmdb_asn,
        ip_blacklist: ref ip_blacklist_str,
        ..
    } = sniffer.conf.settings;
    let ip_blacklist = &sniffer.ip_blacklist;

    let is_editable = sniffer.running_page.is_none();

    let mut column = Column::new()
        .align_x(Alignment::Center)
        .padding([5, 10])
        .push(row_language_scale_factor(language, scale_factor))
        .push(RuleType::Standard.horizontal(25))
        .push(Space::new().height(10));

    if !is_editable {
        column = column
            .push(
                Container::new(Text::new(params_not_editable_translation(language)))
                    .padding(10.0)
                    .class(ContainerType::Badge),
            )
            .push(Space::new().height(10));
    }

    let import_files_row = Row::new()
        .align_y(Alignment::Start)
        .height(100)
        .push(mmdb_settings(
            is_editable,
            language,
            mmdb_country,
            mmdb_asn,
            &sniffer.mmdb_readers,
        ))
        .push(RuleType::Standard.vertical(25))
        .push(blacklist_selection(
            is_editable,
            ip_blacklist_str,
            ip_blacklist,
            language,
        ));

    column = column.push(import_files_row);

    column
}

fn row_language_scale_factor<'a>(
    language: Language,
    scale_factor: f32,
) -> Row<'a, Message, StyleType> {
    Row::new()
        .align_y(Alignment::Start)
        .height(100)
        .push(language_picklist(language))
        .push(RuleType::Standard.vertical(25))
        .push(scale_factor_slider(language, scale_factor))
        .push(RuleType::Standard.vertical(25))
        .push(need_help(language))
}

fn language_picklist<'a>(language: Language) -> Container<'a, Message, StyleType> {
    let mut flag_row = Row::new()
        .align_y(Alignment::Center)
        .spacing(10)
        .push(language.get_flag());
    if !language.is_up_to_date() {
        flag_row = flag_row.push(
            Tooltip::new(
                button(
                    Text::new("!")
                        .class(TextType::Danger)
                        .align_y(Alignment::Center)
                        .align_x(Alignment::Center)
                        .size(15)
                        .line_height(LineHeight::Relative(1.0)),
                )
                .on_press(Message::OpenWebPage(WebPage::IssueLanguages))
                .padding(2)
                .height(20)
                .width(20)
                .class(ButtonType::Alert),
                row_open_link_tooltip("The selected language is not\nfully updated to version 1.5"),
                Position::FollowCursor,
            )
            .class(ContainerType::Tooltip)
            .delay(TOOLTIP_DELAY),
        );
    }

    let content = Column::new()
        .align_x(Alignment::Center)
        .push(
            Text::new(language_translation(language))
                .class(TextType::Subtitle)
                .size(FONT_SIZE_SUBTITLE),
        )
        .push(Space::new().height(Length::Fill))
        .push(flag_row)
        .push(Space::new().height(10))
        .push(
            PickList::new(
                &Language::ALL[..],
                Some(language),
                Message::LanguageSelection,
            )
            .menu_height(200)
            .padding([2, 7]),
        )
        .push(Space::new().height(Length::Fill));

    Container::new(content)
        .width(Length::Fill)
        .align_x(Alignment::Center)
        .align_y(Alignment::Center)
}

fn scale_factor_slider<'a>(
    language: Language,
    scale_factor: f32,
) -> Container<'a, Message, StyleType> {
    let slider_width = 130.0 / scale_factor;
    let slider_val = scale_factor.log(3.0);
    Container::new(
        Column::new()
            .align_x(Alignment::Center)
            .push(
                Text::new(zoom_translation(language))
                    .class(TextType::Subtitle)
                    .size(FONT_SIZE_SUBTITLE),
            )
            .push(Space::new().height(Length::Fill))
            .push(Text::new(format!("{:.0}%", scale_factor * 100.0)))
            .push(Space::new().height(5))
            .push(
                Slider::new(-1.0..=1.0, slider_val, |slider_val| {
                    let scale_factor_str = format!("{:.1}", 3.0_f32.powf(slider_val));
                    let scale_factor = scale_factor_str.parse().unwrap_or(1.0);
                    Message::ChangeScaleFactor(scale_factor)
                })
                .step(0.01)
                .width(slider_width),
            )
            .push(Space::new().height(Length::Fill)),
    )
    .width(Length::Fill)
    .align_x(Alignment::Center)
    .align_y(Alignment::Center)
}

fn need_help<'a>(language: Language) -> Container<'a, Message, StyleType> {
    let content = Column::new()
        .align_x(Alignment::Center)
        .push(
            Text::new(share_feedback_translation(language))
                .class(TextType::Subtitle)
                .size(FONT_SIZE_SUBTITLE),
        )
        .push(Space::new().height(Length::Fill))
        .push(
            Tooltip::new(
                button(
                    Icon::Feedback
                        .to_text()
                        .align_y(Alignment::Center)
                        .align_x(Alignment::Center)
                        .size(20)
                        .line_height(LineHeight::Relative(1.0)),
                )
                .on_press(Message::OpenWebPage(WebPage::Issues))
                .padding(Padding::new(2.0).top(5))
                .height(40)
                .width(60),
                row_open_link_tooltip("GitHub Issues"),
                Position::Right,
            )
            .gap(5)
            .class(ContainerType::Tooltip)
            .delay(TOOLTIP_DELAY),
        )
        .push(Space::new().height(Length::Fill));

    Container::new(content)
        .width(Length::Fill)
        .align_x(Alignment::Center)
        .align_y(Alignment::Center)
}

fn mmdb_settings<'a>(
    is_editable: bool,
    language: Language,
    country_path: &str,
    asn_path: &str,
    mmdb_readers: &MmdbReaders,
) -> Column<'a, Message, StyleType> {
    Column::new()
        .width(Length::Fill)
        .spacing(5)
        .align_x(Alignment::Center)
        .push(
            Text::new(mmdb_files_translation(language))
                .class(TextType::Subtitle)
                .size(FONT_SIZE_SUBTITLE),
        )
        .push(mmdb_selection_row(
            is_editable,
            Message::CustomCountryDb,
            country_path,
            &mmdb_readers.country,
            country_translation(language),
            language,
        ))
        .push(mmdb_selection_row(
            is_editable,
            Message::CustomAsnDb,
            asn_path,
            &mmdb_readers.asn,
            "ASN",
            language,
        ))
}

fn mmdb_selection_row<'a>(
    is_editable: bool,
    message: fn(String) -> Message,
    custom_path: &str,
    mmdb_reader: &MmdbReader,
    caption: &str,
    language: Language,
) -> Row<'a, Message, StyleType> {
    let is_error = if custom_path.is_empty() {
        false
    } else {
        match *mmdb_reader {
            MmdbReader::Default(_) | MmdbReader::Empty => true,
            MmdbReader::Custom(_) => false,
        }
    };

    Row::new()
        .align_y(Alignment::Center)
        .push(Text::new(format!("{caption}: ")))
        .push(
            Text::new(get_path_termination_string(custom_path, 25)).class(if is_error {
                TextType::Danger
            } else {
                TextType::Standard
            }),
        )
        .push(if custom_path.is_empty() {
            button_open_file(
                custom_path.to_owned(),
                FileInfo::Database,
                language,
                is_editable,
                message,
            )
        } else {
            button_clear_mmdb(message, is_editable)
        })
}

fn blacklist_selection<'a>(
    is_editable: bool,
    custom_path: &str,
    ip_blacklist: &IpBlacklist,
    language: Language,
) -> Column<'a, Message, StyleType> {
    let is_error = if custom_path.is_empty() {
        false
    } else {
        ip_blacklist.is_invalid()
    };

    let message = Message::LoadIpBlacklist;

    Column::new()
        .width(Length::Fill)
        .spacing(5)
        .align_x(Alignment::Center)
        .push(
            Text::new(ip_blacklist_translation(language))
                .class(TextType::Subtitle)
                .size(FONT_SIZE_SUBTITLE),
        )
        .push(
            Row::new()
                .align_y(Alignment::Center)
                .push(
                    Text::new(get_path_termination_string(custom_path, 25)).class(if is_error {
                        TextType::Danger
                    } else {
                        TextType::Standard
                    }),
                )
                .push(if custom_path.is_empty() {
                    button_open_file(
                        custom_path.to_owned(),
                        FileInfo::Blacklist,
                        language,
                        is_editable,
                        message,
                    )
                } else {
                    button_clear_mmdb(message, is_editable)
                }),
        )
}

fn button_clear_mmdb<'a>(
    message: fn(String) -> Message,
    is_editable: bool,
) -> Tooltip<'a, Message, StyleType> {
    let mut button = button(
        Text::new("×")
            .align_y(Alignment::Center)
            .align_x(Alignment::Center)
            .size(15)
            .line_height(LineHeight::Relative(1.0)),
    )
    .padding(2)
    .height(20)
    .width(20);

    if is_editable {
        button = button.on_press(message(String::new()));
    }

    Tooltip::new(button, "", Position::Right).delay(TOOLTIP_DELAY)
}