Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target/
.flatpak-builder/
Cargo.lock
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@
- Renamed icon.svg to enroll.svg
0.5.0: Refactored users to nav & fingerprint picker
0.5.1: Added placeholder user icon
0.5.2:
- Implemented Settings menu
- Refactored user options
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cosmic-utils-enroll"
version = "0.5.1"
version = "0.5.2"
edition = "2024"
license = "MPL-2.0"
description = "GUI for fprintd fingerprint enrolling"
Expand Down
1 change: 1 addition & 0 deletions i18n/en/cosmic_utils_enroll.ftl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
app-title = Enroll Fingerprint
about = About
settings = Settings
view = View
fprint = Enroll
welcome = Register and/or delete fingerprints
Expand Down
1 change: 1 addition & 0 deletions i18n/fi/cosmic_utils_enroll.ftl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
app-title = Lisää sormenjälki
about = Tietoa
settings = Asetukset
view = Katso
fprint = Rekisteröi sormenjälki
welcome = Rekisteröi tai poista sormenjälkiä
Expand Down
1 change: 1 addition & 0 deletions i18n/sv/cosmic_utils_enroll.ftl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
app-title = Fprint registrerare
about = Om
settings = Inställningar
view = Visa
fprint = Registrera fingeravtryck
welcome = Registrera och/eller radera fingeravtryck
Expand Down
91 changes: 32 additions & 59 deletions src/app/app.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
// SPDX-License-Identifier: MPL-2.0
use crate::accounts_dbus::{AccountsProxyBlocking, UserProxyBlocking};
use crate::app::MenuAction;
use crate::app::error::*;
use crate::app::finger::*;
use crate::app::fprint::*;
use crate::app::message::*;
use crate::app::message::Message;
use crate::app::users::*;

use crate::app::{ContextPage, MenuAction};
use crate::config::Config;
use crate::fl;
use crate::fprint_dbus::DeviceProxy;

use cosmic::app::context_drawer;
use cosmic::cosmic_config::{self, CosmicConfigEntry};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::widget::pick_list;
use cosmic::iced::{Alignment, Length, Subscription};
use cosmic::iced_widget::pick_list;
use cosmic::prelude::*;
use cosmic::widget::{self, dialog, menu, nav_bar, text};
use cosmic::widget::{self, column, dialog, menu, nav_bar, settings::view_column, text};
use cosmic::{cosmic_theme, theme};

use futures_util::SinkExt;
use nix::unistd::{Uid, User};
use std::collections::HashMap;
use std::sync::Arc;

Expand All @@ -33,53 +33,6 @@ const MAIN_PADDING: u16 = 20;

use super::AppModel;

/// Uses DBus synchronously to initialize users
fn initialize_users() -> (Vec<UserOption>, nav_bar::Model, Option<UserOption>) {
let mut users = Vec::new();

if let Ok(conn) = zbus::blocking::Connection::system() {
if let Ok(accounts) = AccountsProxyBlocking::new(&conn) {
if let Ok(user_paths) = accounts.list_cached_users() {
for path in user_paths {
if let Ok(builder) = UserProxyBlocking::builder(&conn).path(&path) {
if let Ok(user_proxy) = builder.build() {
if let (Ok(name), Ok(real_name), Ok(icon)) = (
user_proxy.user_name(),
user_proxy.real_name(),
user_proxy.icon_file()
) {
users.push(UserOption {
username: Arc::new(name),
realname: Arc::new(real_name),
icon: Arc::new(icon),
});
}
}
}
}
}
}
}

// TODO: to use actual icon need custom nav
let mut nav = nav_bar::Model::default();
let mut selected_user = None;
let current_username = User::from_uid(Uid::current())
.ok()
.flatten()
.map(|u| u.name);

// TODO: use actual icon
for user_opt in &users {
let id = nav.insert().text(user_opt.to_string()).icon(widget::icon::from_name("user-idle-symbolic")).id();
if selected_user.is_none() || current_username.as_deref() == Some(&*user_opt.username) {
nav.activate(id);
selected_user = Some(user_opt.clone());
}
}
(users, nav, selected_user)
}

/// Create a COSMIC application from the app model
impl cosmic::Application for AppModel {
/// The async executor that will be used to run your application's commands.
Expand Down Expand Up @@ -111,7 +64,7 @@ impl cosmic::Application for AppModel {
// Construct the app model with the runtime's core.
let mut app = AppModel {
core,
context_page: ContextPage::default(),
context_page: ContextPage::About,
nav,
key_binds: HashMap::new(),
config: Config::default(),
Expand Down Expand Up @@ -180,7 +133,10 @@ impl cosmic::Application for AppModel {
Element::from(menu::root(fl!("view"))),
menu::items(
&self.key_binds,
vec![menu::Item::Button(fl!("about"), None, MenuAction::About)],
vec![
menu::Item::Button(fl!("about"), None, MenuAction::About),
menu::Item::Button(fl!("settings"), None, MenuAction::Settings),
],
),
)]);

Expand Down Expand Up @@ -208,6 +164,11 @@ impl cosmic::Application for AppModel {
Message::ToggleContextPage(ContextPage::About),
)
.title(fl!("about")),
ContextPage::Settings => context_drawer::context_drawer(
self.settings(),
Message::ToggleContextPage(ContextPage::Settings),
)
.title(fl!("settings")),
})
}

Expand Down Expand Up @@ -237,9 +198,7 @@ impl cosmic::Application for AppModel {
/// Application events will be processed through the view. Any messages emitted by
/// events received by widgets will be passed to the update method.
fn view(&self) -> Element<'_, Self::Message> {
let mut column = widget::column()
.push(self.view_header())
.push(self.view_status());
let mut column = column().push(self.view_header()).push(self.view_status());

if let Some(picker) = self.view_finger_picker() {
column = column.push(picker);
Expand Down Expand Up @@ -390,7 +349,7 @@ impl AppModel {
.on_press(Message::OpenRepositoryUrl)
.padding(0);

widget::column()
column()
.push(icon)
.push(title)
.push(link)
Expand All @@ -408,6 +367,17 @@ impl AppModel {
.into()
}

pub fn settings(&self) -> Element<'_, Message> {
let col = column().push(
widget::checkbox("Experimental UI", self.config.experimental_ui).on_toggle(|value| {
Message::UpdateConfig(Config {
experimental_ui: value,
})
}),
);
view_column(vec![col.into()]).into()
}

fn list_fingers_task(&self) -> Task<cosmic::Action<Message>> {
if let (Some(proxy), Some(user)) = (&self.device_proxy, &self.selected_user) {
let proxy = proxy.clone();
Expand Down Expand Up @@ -717,6 +687,9 @@ impl AppModel {
}

fn view_header(&self) -> Element<'_, Message> {
if self.config.experimental_ui {
return column().into();
}
text::title1(fl!("app-title"))
.apply(widget::container)
.width(Length::Fill)
Expand Down
7 changes: 0 additions & 7 deletions src/app/finger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,6 @@ impl Finger {
}
}

/// The context page to display in the context drawer.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum ContextPage {
#[default]
About,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
76 changes: 1 addition & 75 deletions src/app/message.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// SPDX-License-Identifier: MPL-2.0

use crate::app::ContextPage;
use crate::app::error::AppError;
use crate::app::finger::ContextPage;
use crate::config::Config;
use crate::fprint_dbus::DeviceProxy;
use std::sync::Arc;

/// Messages emitted by the application and its widgets.
#[derive(Debug, Clone)]
Expand All @@ -28,76 +27,3 @@ pub enum Message {
EnrolledFingers(Vec<String>),
FingerSelected(String),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserOption {
pub username: Arc<String>,
pub realname: Arc<String>,
pub icon: Arc<String>,
}

impl std::fmt::Display for UserOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.realname.is_empty() {
write!(f, "{}", self.username)
} else {
write!(f, "{} ({})", self.realname, self.username)
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;

#[test]
fn test_user_option_display_with_realname() {
let user_option = UserOption {
username: Arc::new("jdoe".to_string()),
realname: Arc::new("John Doe".to_string()),
icon: Arc::new("".to_string()),
};
assert_eq!(user_option.to_string(), "John Doe (jdoe)");
}

#[test]
fn test_user_option_display_without_realname() {
let user_option = UserOption {
username: Arc::new("jdoe".to_string()),
realname: Arc::new("".to_string()),
icon: Arc::new("".to_string()),
};
assert_eq!(user_option.to_string(), "jdoe");
}

#[test]
fn test_user_option_display_with_whitespace_realname() {
let user_option = UserOption {
username: Arc::new("jdoe".to_string()),
realname: Arc::new(" ".to_string()),
icon: Arc::new("".to_string()),
};
assert_eq!(user_option.to_string(), " (jdoe)");
}

#[test]
fn test_user_option_display_empty_username() {
let user_option = UserOption {
username: Arc::new("".to_string()),
realname: Arc::new("John Doe".to_string()),
icon: Arc::new("".to_string()),
};
assert_eq!(user_option.to_string(), "John Doe ()");
}

#[test]
fn test_user_option_display_both_empty() {
let user_option = UserOption {
username: Arc::new("".to_string()),
realname: Arc::new("".to_string()),
icon: Arc::new("".to_string()),
};
assert_eq!(user_option.to_string(), "");
}
}
15 changes: 11 additions & 4 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ use std::{collections::HashMap, sync::Arc};
use cosmic::widget::{menu, nav_bar};

use crate::{
app::{
finger::{ContextPage, Finger},
message::{Message, UserOption},
},
app::{finger::Finger, message::Message, users::UserOption},
config::Config,
fprint_dbus::DeviceProxy,
};
Expand All @@ -17,6 +14,7 @@ pub mod error;
pub mod finger;
pub mod fprint;
pub mod message;
pub mod users;

/// The application model stores app-specific state used to describe its interface and
/// drive its logic.
Expand Down Expand Up @@ -64,6 +62,7 @@ mod app;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MenuAction {
About,
Settings,
}

impl menu::action::MenuAction for MenuAction {
Expand All @@ -72,10 +71,18 @@ impl menu::action::MenuAction for MenuAction {
fn message(&self) -> Self::Message {
match self {
MenuAction::About => Message::ToggleContextPage(ContextPage::About),
MenuAction::Settings => Message::ToggleContextPage(ContextPage::Settings),
}
}
}

/// The context page to display in the context drawer.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ContextPage {
About,
Settings,
}

#[cfg(test)]
mod tests {
use crate::app::error::AppError;
Expand Down
Loading