feat(networking): display list of devices on page
This commit is contained in:
parent
2c07dd8bef
commit
c6cd78ec9c
9 changed files with 356 additions and 91 deletions
|
|
@ -447,6 +447,12 @@ impl cosmic::Application for SettingsApp {
|
|||
return self.activate_page(page);
|
||||
}
|
||||
|
||||
crate::pages::Message::Networking(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<networking::Page>() {
|
||||
return page.update(message).map(Into::into);
|
||||
}
|
||||
}
|
||||
|
||||
crate::pages::Message::Panel(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<panel::Page>() {
|
||||
return page.update(message).map(Into::into);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ pub enum Message {
|
|||
ManageWindowShortcuts(input::keyboard::shortcuts::ShortcutMessage),
|
||||
MoveWindowShortcuts(input::keyboard::shortcuts::ShortcutMessage),
|
||||
NavShortcuts(input::keyboard::shortcuts::ShortcutMessage),
|
||||
Networking(networking::Message),
|
||||
Page(Entity),
|
||||
Panel(desktop::panel::Message),
|
||||
PanelApplet(desktop::panel::applets_inner::Message),
|
||||
|
|
|
|||
|
|
@ -5,14 +5,67 @@ pub mod vpn;
|
|||
pub mod wifi;
|
||||
pub mod wired;
|
||||
|
||||
use std::{ffi::OsStr, io, process::ExitStatus};
|
||||
use std::{ffi::OsStr, io, process::ExitStatus, sync::Arc};
|
||||
|
||||
use cosmic_settings_page as page;
|
||||
use anyhow::Context;
|
||||
use cosmic::{widget, Apply, Command, Element};
|
||||
use cosmic_dbus_networkmanager::{
|
||||
interface::enums::{DeviceState, DeviceType},
|
||||
nm::NetworkManager,
|
||||
};
|
||||
use cosmic_settings_page::{self as page, section, Section};
|
||||
use cosmic_settings_subscriptions::network_manager;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use slotmap::SlotMap;
|
||||
|
||||
static NM_CONNECTION_EDITOR: &str = "nm-connection-editor";
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Page;
|
||||
pub struct Page {
|
||||
nm_task: Option<tokio::sync::oneshot::Sender<()>>,
|
||||
devices: Vec<Arc<network_manager::devices::DeviceInfo>>,
|
||||
vpn: page::Entity,
|
||||
wifi: page::Entity,
|
||||
wired: page::Entity,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
/// An error occurred.
|
||||
Error(String),
|
||||
/// Successfully connected to the system dbus.
|
||||
NetworkManagerConnect(
|
||||
(
|
||||
zbus::Connection,
|
||||
tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
),
|
||||
),
|
||||
/// Open the wifi settings page with the selected device.
|
||||
OpenPage {
|
||||
page: page::Entity,
|
||||
device: Option<DeviceVariant>,
|
||||
},
|
||||
/// Update the devices lists
|
||||
UpdateDevices(Vec<Arc<network_manager::devices::DeviceInfo>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DeviceVariant {
|
||||
Wired(Arc<network_manager::devices::DeviceInfo>),
|
||||
WiFi(Arc<network_manager::devices::DeviceInfo>),
|
||||
}
|
||||
|
||||
impl From<Message> for crate::app::Message {
|
||||
fn from(message: Message) -> Self {
|
||||
crate::pages::Message::Networking(message).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for crate::pages::Message {
|
||||
fn from(message: Message) -> Self {
|
||||
crate::pages::Message::Networking(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> cosmic_settings_page::Info {
|
||||
|
|
@ -22,15 +75,248 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
)
|
||||
.title(fl!("network-and-wireless"))
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
crate::slab!(descriptions {
|
||||
vpn_txt = fl!("connections-and-profiles", variant = "vpn");
|
||||
});
|
||||
|
||||
let device_list = Section::default().descriptions(descriptions).view::<Self>(
|
||||
move |_binder, page, section| {
|
||||
let descs = §ion.descriptions;
|
||||
|
||||
let wifi_devices = page
|
||||
.devices
|
||||
.iter()
|
||||
.filter(|device| device.device_type == DeviceType::Wifi)
|
||||
.map(|device| {
|
||||
crate::widget::page_list_item(
|
||||
fl!("wifi", "adapter", id = device.interface.as_str()),
|
||||
match device.state {
|
||||
DeviceState::Activated => fl!("network-device-state", "activated"),
|
||||
DeviceState::Config => fl!("network-device-state", "config"),
|
||||
DeviceState::Deactivating => {
|
||||
fl!("network-device-state", "deactivating")
|
||||
}
|
||||
DeviceState::Disconnected => {
|
||||
fl!("network-device-state", "disconnected")
|
||||
}
|
||||
DeviceState::Failed => fl!("network-device-state", "failed"),
|
||||
DeviceState::IpCheck => fl!("network-device-state", "ip-check"),
|
||||
DeviceState::IpConfig => fl!("network-device-state", "ip-config"),
|
||||
DeviceState::NeedAuth => fl!("network-device-state", "need-auth"),
|
||||
DeviceState::Prepare => fl!("network-device-state", "prepare"),
|
||||
DeviceState::Secondaries => {
|
||||
fl!("network-device-state", "secondaries")
|
||||
}
|
||||
DeviceState::Unavailable => {
|
||||
fl!("network-device-state", "unavailable")
|
||||
}
|
||||
DeviceState::Unknown => fl!("network-device-state", "unknown"),
|
||||
DeviceState::Unmanaged => fl!("network-device-state", "unmanaged"),
|
||||
},
|
||||
"preferences-wireless-symbolic",
|
||||
Message::OpenPage {
|
||||
page: page.wifi,
|
||||
device: Some(DeviceVariant::WiFi(device.clone())),
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let wired_devices = page
|
||||
.devices
|
||||
.iter()
|
||||
.filter(|device| device.device_type == DeviceType::Ethernet)
|
||||
.map(|device| {
|
||||
crate::widget::page_list_item(
|
||||
fl!("wired", "adapter", id = device.interface.as_str()),
|
||||
match device.state {
|
||||
DeviceState::Activated => fl!("network-device-state", "activated"),
|
||||
DeviceState::Config => fl!("network-device-state", "config"),
|
||||
DeviceState::Deactivating => {
|
||||
fl!("network-device-state", "deactivating")
|
||||
}
|
||||
DeviceState::Disconnected => {
|
||||
fl!("network-device-state", "disconnected")
|
||||
}
|
||||
DeviceState::Failed => fl!("network-device-state", "failed"),
|
||||
DeviceState::IpCheck => fl!("network-device-state", "ip-check"),
|
||||
DeviceState::IpConfig => fl!("network-device-state", "ip-config"),
|
||||
DeviceState::NeedAuth => fl!("network-device-state", "need-auth"),
|
||||
DeviceState::Prepare => fl!("network-device-state", "prepare"),
|
||||
DeviceState::Secondaries => {
|
||||
fl!("network-device-state", "secondaries")
|
||||
}
|
||||
DeviceState::Unavailable => {
|
||||
fl!("network-device-state", "unplugged")
|
||||
}
|
||||
DeviceState::Unknown => fl!("network-device-state", "unknown"),
|
||||
DeviceState::Unmanaged => fl!("network-device-state", "unmanaged"),
|
||||
},
|
||||
"preferences-wired-symbolic",
|
||||
Message::OpenPage {
|
||||
page: page.wired,
|
||||
device: Some(DeviceVariant::Wired(device.clone())),
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let device_list = wifi_devices
|
||||
.chain(wired_devices)
|
||||
.fold(widget::column(), |column, device| column.push(device))
|
||||
.push(crate::widget::page_list_item(
|
||||
fl!("vpn"),
|
||||
&descs[vpn_txt],
|
||||
"preferences-vpn-symbolic",
|
||||
Message::OpenPage {
|
||||
page: page.vpn,
|
||||
device: None,
|
||||
},
|
||||
))
|
||||
.spacing(cosmic::theme::active().cosmic().spacing.space_s);
|
||||
|
||||
Element::from(device_list).map(crate::pages::Message::Networking)
|
||||
},
|
||||
);
|
||||
|
||||
Some(vec![sections.insert(device_list)])
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
_page: page::Entity,
|
||||
sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> cosmic::Command<crate::pages::Message> {
|
||||
if self.nm_task.is_none() {
|
||||
return cosmic::command::future(async move {
|
||||
zbus::Connection::system()
|
||||
.await
|
||||
.context("failed to create system dbus connection")
|
||||
.map_or_else(
|
||||
|why| Message::Error(why.to_string()),
|
||||
|conn| Message::NetworkManagerConnect((conn, sender.clone())),
|
||||
)
|
||||
.apply(crate::pages::Message::Networking)
|
||||
});
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Command<crate::pages::Message> {
|
||||
self.devices = Vec::new();
|
||||
|
||||
if let Some(cancel) = self.nm_task.take() {
|
||||
_ = cancel.send(());
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {
|
||||
fn sub_pages(
|
||||
page: cosmic_settings_page::Insert<crate::pages::Message>,
|
||||
mut page: cosmic_settings_page::Insert<crate::pages::Message>,
|
||||
) -> cosmic_settings_page::Insert<crate::pages::Message> {
|
||||
page.sub_page::<wired::Page>()
|
||||
.sub_page::<wifi::Page>()
|
||||
.sub_page::<vpn::Page>()
|
||||
let vpn = page.sub_page_with_id::<vpn::Page>();
|
||||
let wifi = page.sub_page_with_id::<wifi::Page>();
|
||||
let wired = page.sub_page_with_id::<wired::Page>();
|
||||
|
||||
let model = page.model.page_mut::<Self>().unwrap();
|
||||
model.vpn = vpn;
|
||||
model.wifi = wifi;
|
||||
model.wired = wired;
|
||||
|
||||
page
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) -> Command<crate::app::Message> {
|
||||
let span = tracing::span!(tracing::Level::INFO, "networking::update");
|
||||
let _span = span.enter();
|
||||
|
||||
match message {
|
||||
Message::NetworkManagerConnect((conn, output)) => {
|
||||
self.connect(conn.clone(), output);
|
||||
}
|
||||
|
||||
Message::Error(why) => {
|
||||
tracing::error!(why);
|
||||
}
|
||||
|
||||
Message::OpenPage { page, device } => {
|
||||
let mut commands = Vec::<Command<crate::app::Message>>::new();
|
||||
|
||||
commands.push(cosmic::command::message(crate::app::Message::Page(page)));
|
||||
|
||||
if let Some(device) = device {
|
||||
commands.push(cosmic::command::message(crate::app::Message::PageMessage(
|
||||
match device {
|
||||
DeviceVariant::WiFi(device) => {
|
||||
crate::pages::Message::WiFi(wifi::Message::SelectDevice(device))
|
||||
}
|
||||
DeviceVariant::Wired(device) => {
|
||||
crate::pages::Message::Wired(wired::Message::SelectDevice(device))
|
||||
}
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
return cosmic::command::batch(commands);
|
||||
}
|
||||
|
||||
Message::UpdateDevices(devices) => {
|
||||
self.devices = devices;
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn connect(
|
||||
&mut self,
|
||||
conn: zbus::Connection,
|
||||
sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) {
|
||||
if self.nm_task.is_none() {
|
||||
self.nm_task = Some(crate::utils::forward_event_loop(
|
||||
sender,
|
||||
|event| crate::pages::Message::Networking(event),
|
||||
move |mut tx| async move {
|
||||
let network_manager = match NetworkManager::new(&conn).await {
|
||||
Ok(n) => n,
|
||||
Err(why) => {
|
||||
tracing::error!(
|
||||
why = why.to_string(),
|
||||
"failed to connect to network_manager"
|
||||
);
|
||||
|
||||
return futures::future::pending().await;
|
||||
}
|
||||
};
|
||||
|
||||
let mut devices_changed = std::pin::pin!(network_manager
|
||||
.receive_devices_changed()
|
||||
.await
|
||||
.then(|_| async {
|
||||
match network_manager::devices::list(&conn, |_| true).await {
|
||||
Ok(devices) => Message::UpdateDevices(
|
||||
devices.into_iter().map(Arc::new).collect(),
|
||||
),
|
||||
Err(why) => Message::Error(why.to_string()),
|
||||
}
|
||||
}));
|
||||
|
||||
while let Some(message) = devices_changed.next().await {
|
||||
_ = tx.send(message).await;
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ use cosmic_settings_subscriptions::network_manager::{
|
|||
use futures::{FutureExt, StreamExt};
|
||||
use indexmap::IndexMap;
|
||||
use secure_string::SecureString;
|
||||
use slab::Slab;
|
||||
|
||||
pub type ConnectionId = Arc<str>;
|
||||
pub type InterfaceId = String;
|
||||
|
|
@ -285,6 +284,9 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) -> Command<crate::app::Message> {
|
||||
let span = tracing::span!(tracing::Level::INFO, "vpn::update");
|
||||
let _span = span.enter();
|
||||
|
||||
match message {
|
||||
Message::NetworkManager(network_manager::Event::RequestResponse {
|
||||
req,
|
||||
|
|
@ -490,7 +492,7 @@ impl Page {
|
|||
}
|
||||
|
||||
Message::Error(why) => {
|
||||
tracing::error!(why, "error in VPN settings page");
|
||||
tracing::error!(why);
|
||||
}
|
||||
|
||||
Message::NetworkManagerConnect((conn, output)) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright 2024 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use cosmic::{
|
||||
|
|
@ -17,7 +20,6 @@ use cosmic_settings_subscriptions::network_manager::{
|
|||
};
|
||||
use futures::StreamExt;
|
||||
use secure_string::SecureString;
|
||||
use slab::Slab;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
|
|
@ -52,6 +54,8 @@ pub enum Message {
|
|||
PasswordRequest(network_manager::SSID),
|
||||
/// Update the password from the dialog
|
||||
PasswordUpdate(SecureString),
|
||||
/// Selects a device to display connections from
|
||||
SelectDevice(Arc<network_manager::devices::DeviceInfo>),
|
||||
/// Opens settings page for the access point.
|
||||
Settings(network_manager::SSID),
|
||||
/// Toggles visibility of the password input
|
||||
|
|
@ -92,6 +96,8 @@ enum WiFiDialog {
|
|||
pub struct Page {
|
||||
nm_task: Option<tokio::sync::oneshot::Sender<()>>,
|
||||
nm_state: Option<NmState>,
|
||||
/// When defined, displays connections for the specific device.
|
||||
active_device: Option<Arc<network_manager::devices::DeviceInfo>>,
|
||||
dialog: Option<WiFiDialog>,
|
||||
view_more_popup: Option<network_manager::SSID>,
|
||||
connecting: BTreeSet<network_manager::SSID>,
|
||||
|
|
@ -211,6 +217,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
}
|
||||
|
||||
fn on_leave(&mut self) -> Command<crate::pages::Message> {
|
||||
self.active_device = None;
|
||||
self.view_more_popup = None;
|
||||
self.nm_state = None;
|
||||
self.ssid_to_uuid.clear();
|
||||
|
|
@ -228,6 +235,9 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) -> Command<crate::app::Message> {
|
||||
let span = tracing::span!(tracing::Level::INFO, "vpn::update");
|
||||
let _span = span.enter();
|
||||
|
||||
match message {
|
||||
Message::NetworkManager(network_manager::Event::RequestResponse {
|
||||
req,
|
||||
|
|
@ -421,7 +431,12 @@ impl Page {
|
|||
}
|
||||
|
||||
Message::Error(why) => {
|
||||
tracing::error!(why, "error in wifi settings page");
|
||||
tracing::error!(why);
|
||||
}
|
||||
|
||||
Message::SelectDevice(device) => {
|
||||
// TODO: Per-device wifi connection handling.
|
||||
self.active_device = Some(device);
|
||||
}
|
||||
|
||||
Message::NetworkManagerConnect((conn, output)) => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ use cosmic_settings_page::{self as page, section, Section};
|
|||
use cosmic_settings_subscriptions::network_manager::{
|
||||
self, current_networks::ActiveConnectionInfo, devices::DeviceState, NetworkManagerState,
|
||||
};
|
||||
use slab::Slab;
|
||||
|
||||
pub type ConnectionId = Arc<str>;
|
||||
|
||||
|
|
@ -196,6 +195,9 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) -> Command<crate::app::Message> {
|
||||
let span = tracing::span!(tracing::Level::INFO, "vpn::update");
|
||||
let _span = span.enter();
|
||||
|
||||
match message {
|
||||
Message::NetworkManager(network_manager::Event::RequestResponse {
|
||||
req,
|
||||
|
|
@ -346,7 +348,7 @@ impl Page {
|
|||
}
|
||||
|
||||
Message::Error(why) => {
|
||||
tracing::error!(why, "error in wired settings page");
|
||||
tracing::error!(why);
|
||||
}
|
||||
|
||||
Message::NetworkManagerConnect((conn, output)) => {
|
||||
|
|
@ -539,66 +541,6 @@ impl Page {
|
|||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn device_list_view<'a>(
|
||||
&'a self,
|
||||
_spacing: &cosmic::cosmic_theme::Spacing,
|
||||
nm_state: &'a NmState,
|
||||
devices_txt: &'a str,
|
||||
) -> Element<'a, Message> {
|
||||
nm_state
|
||||
.devices
|
||||
.iter()
|
||||
.fold(
|
||||
widget::settings::view_section(devices_txt),
|
||||
|section, device| {
|
||||
let is_unplugged = matches!(device.state, DeviceState::Unavailable);
|
||||
|
||||
let device_list =
|
||||
cosmic::widget::settings::item::builder(device.interface.as_str())
|
||||
.description(match device.state {
|
||||
DeviceState::Activated => fl!("network-device-state", "activated"),
|
||||
DeviceState::Config => fl!("network-device-state", "config"),
|
||||
DeviceState::Deactivating => {
|
||||
fl!("network-device-state", "deactivating")
|
||||
}
|
||||
DeviceState::Disconnected => {
|
||||
fl!("network-device-state", "disconnected")
|
||||
}
|
||||
DeviceState::Failed => fl!("network-device-state", "failed"),
|
||||
DeviceState::IpCheck => fl!("network-device-state", "ip-check"),
|
||||
DeviceState::IpConfig => fl!("network-device-state", "ip-config"),
|
||||
DeviceState::NeedAuth => fl!("network-device-state", "need-auth"),
|
||||
DeviceState::Prepare => fl!("network-device-state", "prepare"),
|
||||
DeviceState::Secondaries => {
|
||||
fl!("network-device-state", "secondaries")
|
||||
}
|
||||
DeviceState::Unavailable => {
|
||||
fl!("network-device-state", "unplugged")
|
||||
}
|
||||
DeviceState::Unknown => fl!("network-device-state", "unknown"),
|
||||
DeviceState::Unmanaged => fl!("network-device-state", "unmanaged"),
|
||||
})
|
||||
.icon(icon::from_name("network-wired-symbolic").size(32))
|
||||
.control(icon::from_name("go-next-symbolic").size(20))
|
||||
.spacing(16)
|
||||
.apply(widget::container)
|
||||
.padding([16, 14])
|
||||
.style(cosmic::theme::Container::List)
|
||||
.apply(widget::button)
|
||||
.padding(0)
|
||||
.style(cosmic::theme::Button::Transparent)
|
||||
.on_press_maybe(if is_unplugged {
|
||||
None
|
||||
} else {
|
||||
Some(Message::SelectDevice(device.clone()))
|
||||
});
|
||||
|
||||
section.add(device_list)
|
||||
},
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn devices_view() -> Section<crate::pages::Message> {
|
||||
|
|
@ -644,11 +586,7 @@ fn devices_view() -> Section<crate::pages::Message> {
|
|||
device,
|
||||
)),
|
||||
|
||||
None => view.push(page.device_list_view(
|
||||
spacing,
|
||||
nm_state,
|
||||
§ion.descriptions[wired_devices_txt],
|
||||
)),
|
||||
None => view,
|
||||
};
|
||||
|
||||
view.spacing(spacing.space_l)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ pub fn forward_event_loop<M: 'static + Send, T: Future<Output = ()> + Send + 'st
|
|||
#[macro_export]
|
||||
macro_rules! slab {
|
||||
( $descriptions:ident { $( $txt_id:ident = $txt_expr:expr; )+ } ) => {
|
||||
let mut $descriptions = Slab::new();
|
||||
let mut $descriptions = slab::Slab::new();
|
||||
|
||||
$(
|
||||
let $txt_id = $descriptions.insert($txt_expr);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use cosmic::cosmic_theme::Spacing;
|
||||
use cosmic::iced::{alignment, Length};
|
||||
use cosmic::iced_core::text::Wrap;
|
||||
use cosmic::prelude::CollectionWidget;
|
||||
|
|
@ -118,18 +119,32 @@ pub fn display_container<'a, Message: 'a>(widget: Element<'a, Message>) -> Eleme
|
|||
|
||||
#[must_use]
|
||||
pub fn page_list_item<'a, Message: 'static + Clone>(
|
||||
title: &'a str,
|
||||
description: &'a str,
|
||||
title: impl Into<Cow<'a, str>>,
|
||||
description: impl Into<Cow<'a, str>>,
|
||||
icon: &'a str,
|
||||
message: Message,
|
||||
) -> Element<'a, Message> {
|
||||
cosmic::widget::settings::item::builder(title)
|
||||
.description(description)
|
||||
let Spacing {
|
||||
space_s, space_m, ..
|
||||
} = cosmic::theme::active().cosmic().spacing;
|
||||
|
||||
let mut builder = cosmic::widget::settings::item::builder(title);
|
||||
|
||||
let description = description.into();
|
||||
|
||||
if !description.is_empty() {
|
||||
builder = builder.description(description);
|
||||
}
|
||||
|
||||
builder
|
||||
.icon(icon::from_name(icon).size(20))
|
||||
.control(icon::from_name("go-next-symbolic").size(20))
|
||||
.spacing(16)
|
||||
.spacing(space_s)
|
||||
.height(space_s + space_m)
|
||||
.align_items(alignment::Alignment::Center)
|
||||
.apply(container)
|
||||
.padding([16, 14])
|
||||
.padding([space_s, space_m])
|
||||
.align_x(alignment::Horizontal::Center)
|
||||
.style(theme::Container::List)
|
||||
.apply(button)
|
||||
.padding(0)
|
||||
|
|
|
|||
|
|
@ -40,15 +40,15 @@ forget-dialog = Forget this Wi-Fi network?
|
|||
.description = You'll need to enter a password again to use this Wi-Fi network in the future.
|
||||
|
||||
network-device-state =
|
||||
.activated = Connected to network
|
||||
.config = Connecting to network
|
||||
.deactivating = Disconnecting from network
|
||||
.activated = Connected
|
||||
.config = Connecting
|
||||
.deactivating = Disconnecting
|
||||
.disconnected = Disconnected
|
||||
.failed = Failed to connect
|
||||
.ip-check = Checking connection
|
||||
.ip-config = Requesting IP and routing information
|
||||
.ip-config = Requesting IP and routing info
|
||||
.need-auth = Needs authentication
|
||||
.prepare = Preparing to connect to network
|
||||
.prepare = Preparing to connect
|
||||
.secondaries = Waiting for secondary connection
|
||||
.unavailable = Unavailable
|
||||
.unknown = Unknown state
|
||||
|
|
@ -65,11 +65,13 @@ vpn = VPN
|
|||
.select-file = Select a VPN configuration file
|
||||
|
||||
wired = Wired
|
||||
.adapter = Wired adapter { $id }
|
||||
.connections = Wired Connections
|
||||
.devices = Wired Devices
|
||||
.remove = Remove connection profile
|
||||
|
||||
wifi = Wi-Fi
|
||||
.adapter = Wi-Fi adapter { $id }
|
||||
.forget = Forget this network
|
||||
|
||||
## Networking: Online Accounts
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue