providers: move out of preferences

This commit is contained in:
Bilal Elmoussaoui 2020-12-05 03:40:12 +01:00
parent ddddb97c5f
commit f55900272c
20 changed files with 606 additions and 180 deletions

1
Cargo.lock generated
View file

@ -282,6 +282,7 @@ dependencies = [
"quick-xml", "quick-xml",
"secret-service", "secret-service",
"surf", "surf",
"unicase",
"url", "url",
"zbar-rust", "zbar-rust",
] ]

View file

@ -21,6 +21,7 @@ url = "2.1"
zbar-rust = "0.0" zbar-rust = "0.0"
secret-service = "1.1" secret-service = "1.1"
once_cell = "1.5" once_cell = "1.5"
unicase = "2.6"
[dependencies.gtk] [dependencies.gtk]
git = "https://github.com/gtk-rs/gtk4-rs" git = "https://github.com/gtk-rs/gtk4-rs"

View file

@ -9,7 +9,9 @@
<file compressed="true" preprocess="xml-stripblanks" alias="account_row.ui">resources/ui/account_row.ui</file> <file compressed="true" preprocess="xml-stripblanks" alias="account_row.ui">resources/ui/account_row.ui</file>
<!-- Providers --> <!-- Providers -->
<file compressed="true" preprocess="xml-stripblanks" alias="provider_page.ui">resources/ui/provider_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="provider_row.ui">resources/ui/provider_row.ui</file> <file compressed="true" preprocess="xml-stripblanks" alias="provider_row.ui">resources/ui/provider_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="providers_all.ui">resources/ui/providers_all.ui</file>
<file compressed="true" preprocess="xml-stripblanks">providers_list.ui</file> <file compressed="true" preprocess="xml-stripblanks">providers_list.ui</file>
<!-- UI Files --> <!-- UI Files -->
@ -18,8 +20,6 @@
<file>window.ui</file> <file>window.ui</file>
<file compressed="true" preprocess="xml-stripblanks">preferences.ui</file> <file compressed="true" preprocess="xml-stripblanks">preferences.ui</file>
<file compressed="true" preprocess="xml-stripblanks">preferences_password_page.ui</file> <file compressed="true" preprocess="xml-stripblanks">preferences_password_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="preferences_provider_page.ui">resources/ui/preferences_provider_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="preferences_providers_page.ui">resources/ui/preferences_providers_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="provider_image.ui">resources/ui/provider_image.ui</file> <file compressed="true" preprocess="xml-stripblanks" alias="provider_image.ui">resources/ui/provider_image.ui</file>
</gresource> </gresource>

View file

@ -50,8 +50,6 @@
<property name="margin-top">12</property> <property name="margin-top">12</property>
<property name="margin-bottom">12</property> <property name="margin-bottom">12</property>
<property name="vexpand">True</property> <property name="vexpand">True</property>
<property name="maximum-size">400</property>
<property name="tightening-threshold">400</property>
<child> <child>
<object class="GtkBox" id="main_container"> <object class="GtkBox" id="main_container">
<property name="orientation">vertical</property> <property name="orientation">vertical</property>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="HdyPreferencesPage" id="providers_page">
<property name="icon-name">package-x-generic-symbolic</property>
<property name="title">Providers</property>
<child>
<object class="GtkScrolledWindow">
<child>
<object class="HdyClampScrollable">
<property name="vexpand">True</property>
<property name="hexpand">True</property>
<child>
<object class="GtkListView" id="providers_list">
<property name="vexpand">true</property>
<property name="single_click_activate">True</property>
<style>
<class name="content" />
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View file

@ -9,10 +9,11 @@
<widgets> <widgets>
<widget name="period_spinbutton"/> <widget name="period_spinbutton"/>
<widget name="provider_website_entry"/> <widget name="provider_website_entry"/>
<widget name="provider_help_entry"/>
<widget name="name_entry"/> <widget name="name_entry"/>
</widgets> </widgets>
</object> </object>
<object class="GtkBox" id="provider_page"> <template parent="GtkBox" class="ProviderPage">
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<child> <child>
@ -20,6 +21,8 @@
<property name="show-title-buttons">False</property> <property name="show-title-buttons">False</property>
<property name="title-widget"> <property name="title-widget">
<object class="GtkLabel" id="title"> <object class="GtkLabel" id="title">
<property name="wrap">True</property>
<property name="ellipsize">end</property>
<style> <style>
<class name="title" /> <class name="title" />
</style> </style>
@ -28,7 +31,7 @@
<child type="start"> <child type="start">
<object class="GtkButton" id="back_btn"> <object class="GtkButton" id="back_btn">
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="action_name">preferences.close_page</property> <property name="action_name">providers.back</property>
<property name="icon-name">go-previous-symbolic</property> <property name="icon-name">go-previous-symbolic</property>
</object> </object>
</child> </child>
@ -37,7 +40,7 @@
<property name="label" translatable="yes">Save</property> <property name="label" translatable="yes">Save</property>
<property name="sensitive">False</property> <property name="sensitive">False</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="action_name">provider.save</property> <property name="action_name">providers.save</property>
<style> <style>
<class name="suggested-action"/> <class name="suggested-action"/>
</style> </style>
@ -51,8 +54,6 @@
<property name="margin-top">12</property> <property name="margin-top">12</property>
<property name="margin-bottom">12</property> <property name="margin-bottom">12</property>
<property name="vexpand">True</property> <property name="vexpand">True</property>
<property name="maximum-size">400</property>
<property name="tightening-threshold">400</property>
<child> <child>
<object class="GtkBox" id="main_container"> <object class="GtkBox" id="main_container">
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
@ -113,7 +114,21 @@
<property name="valign">center</property> <property name="valign">center</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="input_purpose">url</property> <property name="input_purpose">url</property>
<property name="enable_emoji_completion">True</property> </object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="activatable_widget">provider_help_entry</property>
<property name="title" translatable="yes">Help URL</property>
<property name="subtitle" translatable="yes">How to setup Two-Factor Authentication</property>
<child>
<object class="GtkEntry" id="provider_help_entry">
<property name="halign">end</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="input_purpose">url</property>
</object> </object>
</child> </child>
</object> </object>
@ -151,5 +166,5 @@
</child> </child>
</object> </object>
</child> </child>
</object> </template>
</interface> </interface>

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template parent="HdyWindow" class="ProvidersDialog">
<property name="modal">True</property>
<property name="title" translatable="yes">Providers</property>
<property name="default-width">360</property>
<property name="default-height">600</property>
<child>
<object class="HdyLeaflet" id="deck">
<property name="can-unfold">False</property>
<child>
<object class="HdyLeafletPage">
<property name="name">providers</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkHeaderBar">
<property name="show-title-buttons">True</property>
<child type="start">
<object class="GtkButton" id="add_btn">
<property name="receives_default">True</property>
<property name="action_name">providers.add</property>
<property name="icon-name">list-add-symbolic</property>
</object>
</child>
<child type="end">
<object class="GtkToggleButton" id="search_btn">
<property name="receives_default">True</property>
<property name="icon-name">system-search-symbolic</property>
</object>
</child>
<style>
<class name="titlebar"/>
</style>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="min-content-width">340</property>
<property name="min-content-height">400</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar" id="search_bar">
<child>
<object class="GtkSearchEntry" id="search_entry">
</object>
</child>
</object>
</child>
<child>
<object class="HdyClampScrollable">
<property name="vexpand">True</property>
<property name="hexpand">True</property>
<child>
<object class="GtkListView" id="providers_list">
<property name="vexpand">true</property>
<property name="single_click_activate">True</property>
<style>
<class name="content" />
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<menu id="menu"> <menu id="menu">
<item>
<attribute name="label">_Providers</attribute>
<attribute name="action">app.providers</attribute>
</item>
<item> <item>
<attribute name="label">_Lock the application</attribute> <attribute name="label">_Lock the application</attribute>
<attribute name="action">app.lock</attribute> <attribute name="action">app.lock</attribute>

View file

@ -1,7 +1,7 @@
use crate::config; use crate::config;
use crate::helpers::Keyring; use crate::helpers::Keyring;
use crate::models::{Account, Provider, ProvidersModel}; use crate::models::{Account, Provider, ProvidersModel};
use crate::widgets::{AccountAddDialog, PreferencesWindow, View, Window}; use crate::widgets::{AccountAddDialog, PreferencesWindow, ProvidersDialog, View, Window};
use gio::prelude::*; use gio::prelude::*;
use glib::subclass::prelude::*; use glib::subclass::prelude::*;
use glib::{subclass, WeakRef}; use glib::{subclass, WeakRef};
@ -149,6 +149,17 @@ impl ApplicationImpl for ApplicationPrivate {
}) })
); );
action!(
app,
"providers",
clone!(@strong app, @weak self.model as model => move |_, _| {
let window = app.get_active_window().unwrap();
let providers = ProvidersDialog::new(model);
providers.set_transient_for(Some(&window));
providers.show();
})
);
action!( action!(
app, app,
"lock", "lock",
@ -159,10 +170,12 @@ impl ApplicationImpl for ApplicationPrivate {
app.bind_property("can-be-locked", &get_action!(app, @lock), "enabled") app.bind_property("can-be-locked", &get_action!(app, @lock), "enabled")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE) .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
.build(); .build();
app.bind_property("locked", &get_action!(app, @preferences), "enabled") app.bind_property("locked", &get_action!(app, @preferences), "enabled")
.flags(glib::BindingFlags::INVERT_BOOLEAN | glib::BindingFlags::SYNC_CREATE) .flags(glib::BindingFlags::INVERT_BOOLEAN | glib::BindingFlags::SYNC_CREATE)
.build(); .build();
app.bind_property("locked", &get_action!(app, @providers), "enabled")
.flags(glib::BindingFlags::INVERT_BOOLEAN | glib::BindingFlags::SYNC_CREATE)
.build();
} }
fn activate(&self, app: &Self::Type) { fn activate(&self, app: &Self::Type) {
@ -181,10 +194,12 @@ impl ApplicationImpl for ApplicationPrivate {
app.set_resource_base_path(Some("/com/belmoussaoui/Authenticator")); app.set_resource_base_path(Some("/com/belmoussaoui/Authenticator"));
app.set_accels_for_action("app.quit", &["<primary>q"]); app.set_accels_for_action("app.quit", &["<primary>q"]);
app.set_accels_for_action("app.lock", &["<primary>l"]); app.set_accels_for_action("app.lock", &["<primary>l"]);
app.set_accels_for_action("app.providers", &["<primary>p"]);
app.set_accels_for_action("app.preferences", &["<primary>comma"]); app.set_accels_for_action("app.preferences", &["<primary>comma"]);
app.set_accels_for_action("win.show-help-overlay", &["<primary>question"]); app.set_accels_for_action("win.show-help-overlay", &["<primary>question"]);
app.set_accels_for_action("win.search", &["<primary>f"]); app.set_accels_for_action("win.search", &["<primary>f"]);
app.set_accels_for_action("win.add-account", &["<primary>n"]); app.set_accels_for_action("win.add-account", &["<primary>n"]);
app.set_locked(has_set_password); app.set_locked(has_set_password);
app.set_can_be_locked(has_set_password); app.set_can_be_locked(has_set_password);
let receiver = self.receiver.borrow_mut().take().unwrap(); let receiver = self.receiver.borrow_mut().take().unwrap();

View file

@ -44,6 +44,7 @@ sources = files(
'helpers/mod.rs', 'helpers/mod.rs',
'helpers/qrcode.rs', 'helpers/qrcode.rs',
'models/account.rs', 'models/account.rs',
'models/algorithm.rs',
'models/database.rs', 'models/database.rs',
'models/favicon.rs', 'models/favicon.rs',
'models/mod.rs', 'models/mod.rs',
@ -52,14 +53,14 @@ sources = files(
'widgets/accounts/add.rs', 'widgets/accounts/add.rs',
'widgets/accounts/mod.rs', 'widgets/accounts/mod.rs',
'widgets/accounts/row.rs', 'widgets/accounts/row.rs',
'widgets/providers/list.rs',
'widgets/providers/mod.rs',
'widgets/providers/row.rs',
'widgets/preferences/mod.rs', 'widgets/preferences/mod.rs',
'widgets/preferences/password_page.rs', 'widgets/preferences/password_page.rs',
'widgets/preferences/provider_page.rs',
'widgets/preferences/providers_page.rs',
'widgets/preferences/window.rs', 'widgets/preferences/window.rs',
'widgets/providers/all.rs',
'widgets/providers/list.rs',
'widgets/providers/mod.rs',
'widgets/providers/page.rs',
'widgets/providers/row.rs',
'widgets/mod.rs', 'widgets/mod.rs',
'widgets/window.rs', 'widgets/window.rs',
'application.rs', 'application.rs',

View file

@ -10,7 +10,6 @@ pub enum Algorithm {
OTP = 0, OTP = 0,
#[genum(name = "HOTP")] #[genum(name = "HOTP")]
HOTP = 1, HOTP = 1,
#[genum(name = "Steam")]
Steam = 2, Steam = 2,
} }

View file

@ -12,6 +12,7 @@ use gtk::FilterListModelExt;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::str::FromStr; use std::str::FromStr;
use std::string::ToString; use std::string::ToString;
use unicase::UniCase;
use url::Url; use url::Url;
#[derive(Insertable)] #[derive(Insertable)]
@ -250,7 +251,7 @@ impl Provider {
let provider1 = obj1.downcast_ref::<Provider>().unwrap(); let provider1 = obj1.downcast_ref::<Provider>().unwrap();
let provider2 = obj2.downcast_ref::<Provider>().unwrap(); let provider2 = obj2.downcast_ref::<Provider>().unwrap();
provider1.name().cmp(&provider2.name()) UniCase::new(provider1.name()).cmp(&UniCase::new(provider2.name()))
} }
pub fn load() -> Result<Vec<Self>> { pub fn load() -> Result<Vec<Self>> {

View file

@ -5,5 +5,5 @@ mod window;
pub use self::accounts::AccountAddDialog; pub use self::accounts::AccountAddDialog;
pub use self::preferences::PreferencesWindow; pub use self::preferences::PreferencesWindow;
pub use self::providers::ProvidersList; pub use self::providers::{ProvidersDialog, ProvidersList};
pub use self::window::{View, Window}; pub use self::window::{View, Window};

View file

@ -1,6 +1,4 @@
mod password_page; mod password_page;
mod provider_page;
mod providers_page;
mod window; mod window;
pub use window::PreferencesWindow; pub use window::PreferencesWindow;

View file

@ -1,63 +0,0 @@
use crate::models::{Algorithm, Provider};
use glib::translate::ToGlib;
use gtk::prelude::*;
use libhandy::{ComboRowExt, EnumListModelExt};
use std::rc::Rc;
pub struct ProviderPage {
pub widget: gtk::Box,
builder: gtk::Builder,
algorithms_model: libhandy::EnumListModel,
}
impl ProviderPage {
pub fn new() -> Rc<Self> {
let builder = gtk::Builder::from_resource(
"/com/belmoussaoui/Authenticator/preferences_provider_page.ui",
);
get_widget!(builder, gtk::Box, provider_page);
let algorithms_model = libhandy::EnumListModel::new(Algorithm::static_type());
let page = Rc::new(Self {
widget: provider_page,
builder,
algorithms_model,
});
page.init();
page
}
pub fn set_provider(&self, provider: Provider) {
get_widget!(self.builder, gtk::Entry, @name_entry).set_text(&provider.name());
get_widget!(self.builder, gtk::SpinButton, @period_spinbutton)
.set_value(provider.period() as f64);
if let Some(ref website) = provider.website() {
get_widget!(self.builder, gtk::Entry, @provider_website_entry).set_text(website);
}
get_widget!(self.builder, gtk::Stack, @image_stack).set_visible_child_name("loading");
get_widget!(self.builder, gtk::Spinner, @spinner).start();
get_widget!(self.builder, libhandy::ComboRow, algorithm_comborow);
algorithm_comborow.set_selected(
self.algorithms_model
.find_position(provider.algorithm().to_glib()),
);
/*let sender = self.sender.clone();
spawn!(async move {
if let Ok(file) = p.favicon().await {
send!(sender, AddAccountAction::SetIcon(file));
}
});*/
get_widget!(self.builder, gtk::Label, title);
title.set_text(&format!("Editing provider: {}", provider.name()));
}
fn init(&self) {
get_widget!(self.builder, libhandy::ComboRow, algorithm_comborow);
algorithm_comborow.set_model(Some(&self.algorithms_model));
}
}

View file

@ -1,67 +0,0 @@
use crate::models::{Provider, ProvidersModel};
use gio::ListModelExt;
use gtk::prelude::*;
use std::rc::Rc;
pub struct ProvidersPage {
pub widget: libhandy::PreferencesPage,
builder: gtk::Builder,
model: Rc<ProvidersModel>,
}
impl ProvidersPage {
pub fn new(model: Rc<ProvidersModel>) -> Rc<Self> {
let builder = gtk::Builder::from_resource(
"/com/belmoussaoui/Authenticator/preferences_providers_page.ui",
);
get_widget!(builder, libhandy::PreferencesPage, providers_page);
let page = Rc::new(Self {
widget: providers_page,
builder,
model,
});
page.init();
page
}
fn init(&self) {
get_widget!(self.builder, gtk::ListView, providers_list);
let factory = gtk::SignalListItemFactory::new();
factory.connect_bind(|_, list_item| {
let item = list_item.get_item().unwrap();
let provider = item.downcast_ref::<Provider>().unwrap();
let row = Row::new(provider);
list_item.set_child(Some(&row.widget));
});
providers_list.set_factory(Some(&factory));
let selection_model = gtk::NoSelection::new(Some(&self.model.model));
providers_list.set_model(Some(&selection_model));
providers_list.connect_activate(move |listview, pos| {
let model = listview.get_model().unwrap();
let provider = model
.get_object(pos)
.unwrap()
.downcast::<Provider>()
.unwrap();
// send!(sender, PreferencesAction::EditProvider(provider));
});
}
}
pub struct Row<'a> {
pub widget: libhandy::ActionRow,
provider: &'a Provider,
}
impl<'a> Row<'a> {
pub fn new(provider: &'a Provider) -> Self {
let widget = libhandy::ActionRowBuilder::new()
.title(&provider.name())
.build();
Self { widget, provider }
}
}

View file

@ -0,0 +1,311 @@
use super::{ProviderPage, ProviderPageMode};
use crate::models::{Provider, ProvidersModel};
use gio::prelude::*;
use gio::subclass::ObjectSubclass;
use gio::ListModelExt;
use glib::subclass::prelude::*;
use glib::{glib_object_subclass, glib_wrapper};
use gtk::{prelude::*, CompositeTemplate};
use libhandy::{LeafletExt, LeafletPageExt};
use row::ProviderActionRow;
use std::rc::Rc;
mod imp {
use super::*;
use glib::subclass;
use gtk::subclass::prelude::*;
use libhandy::subclass::window::WindowImpl as HdyWindowImpl;
#[derive(CompositeTemplate)]
pub struct ProvidersDialog {
#[template_child(id = "providers_list")]
pub providers_list: TemplateChild<gtk::ListView>,
#[template_child(id = "deck")]
pub deck: TemplateChild<libhandy::Leaflet>,
#[template_child(id = "search_entry")]
pub search_entry: TemplateChild<gtk::SearchEntry>,
#[template_child(id = "search_bar")]
pub search_bar: TemplateChild<gtk::SearchBar>,
#[template_child(id = "search_btn")]
pub search_btn: TemplateChild<gtk::ToggleButton>,
pub page: ProviderPage,
pub actions: gio::SimpleActionGroup,
pub filter_model: gtk::FilterListModel,
}
impl ObjectSubclass for ProvidersDialog {
const NAME: &'static str = "ProvidersDialog";
type Type = super::ProvidersDialog;
type ParentType = libhandy::Window;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn new() -> Self {
let filter_model =
gtk::FilterListModel::new(gtk::NONE_FILTER_LIST_MODEL, gtk::NONE_FILTER);
Self {
deck: TemplateChild::default(),
providers_list: TemplateChild::default(),
search_entry: TemplateChild::default(),
search_bar: TemplateChild::default(),
search_btn: TemplateChild::default(),
page: ProviderPage::new(),
actions: gio::SimpleActionGroup::new(),
filter_model,
}
}
fn class_init(klass: &mut Self::Class) {
klass.set_template_from_resource("/com/belmoussaoui/Authenticator/providers_all.ui");
Self::bind_template_children(klass);
}
}
impl ObjectImpl for ProvidersDialog {
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
self.parent_constructed(obj);
}
}
impl WidgetImpl for ProvidersDialog {}
impl WindowImpl for ProvidersDialog {}
impl HdyWindowImpl for ProvidersDialog {}
}
glib_wrapper! {
pub struct ProvidersDialog(ObjectSubclass<imp::ProvidersDialog>) @extends gtk::Widget, gtk::Window, libhandy::Window;
}
impl ProvidersDialog {
pub fn new(model: Rc<ProvidersModel>) -> Self {
let dialog = glib::Object::new(Self::static_type(), &[])
.expect("Failed to create ProvidersDialog")
.downcast::<ProvidersDialog>()
.expect("Created object is of wrong type");
dialog.setup_widgets(model);
dialog.setup_actions();
dialog
}
fn setup_widgets(&self, model: Rc<ProvidersModel>) {
let self_ = imp::ProvidersDialog::from_instance(self);
self_.filter_model.set_model(Some(&model.model));
self_
.search_bar
.get()
.bind_property("search-mode-enabled", &self_.search_btn.get(), "active")
.flags(glib::BindingFlags::BIDIRECTIONAL | glib::BindingFlags::SYNC_CREATE)
.build();
self_.search_entry.get().connect_search_changed(
clone!(@weak self as dialog => move |entry| {
let text = entry.get_text().unwrap().to_string();
dialog.search(text);
}),
);
let search_btn = self_.search_btn.get();
self_
.search_btn
.get()
.bind_property("active", &self_.search_bar.get(), "search-mode-enabled")
.flags(glib::BindingFlags::BIDIRECTIONAL | glib::BindingFlags::SYNC_CREATE)
.build();
let factory = gtk::SignalListItemFactory::new();
factory.connect_bind(|_, list_item| {
let item = list_item.get_item().unwrap();
let provider = item.clone().downcast::<Provider>().unwrap();
let row = ProviderActionRow::new(provider);
list_item.set_child(Some(&row));
});
self_.providers_list.get().set_factory(Some(&factory));
let selection_model = gtk::NoSelection::new(Some(&self_.filter_model));
self_.providers_list.get().set_model(Some(&selection_model));
self_.providers_list.get().connect_activate(
clone!(@weak self as dialog => move |listview, pos| {
let model = listview.get_model().unwrap();
let provider = model
.get_object(pos)
.unwrap()
.downcast::<Provider>()
.unwrap();
dialog.edit_provider(provider);
}),
);
let deck_page = self_.deck.get().add(&self_.page).unwrap();
deck_page.set_name("provider");
}
fn setup_actions(&self) {
let self_ = imp::ProvidersDialog::from_instance(self);
let deck = self_.deck.get();
let search_bar = self_.search_bar.get();
action!(
self_.actions,
"search",
clone!(@weak search_bar => move |_,_| {
search_bar.set_search_mode(!search_bar.get_search_mode());
})
);
action!(
self_.actions,
"back",
clone!(@weak deck => move |_ , _| {
deck.set_visible_child_name("providers");
})
);
action!(
self_.actions,
"add",
clone!(@weak self as dialog => move |_, _| {
dialog.add_provider();
})
);
self.insert_action_group("providers", Some(&self_.actions));
}
fn search(&self, text: String) {
let self_ = imp::ProvidersDialog::from_instance(self);
let providers_filter = gtk::CustomFilter::new(Some(Box::new(move |object| {
let provider = object.downcast_ref::<Provider>().unwrap();
provider
.name()
.to_ascii_lowercase()
.contains(&text.to_ascii_lowercase())
})));
self_.filter_model.set_filter(Some(&providers_filter));
}
fn add_provider(&self) {
let self_ = imp::ProvidersDialog::from_instance(self);
self_.deck.get().set_visible_child_name("provider");
self_.page.set_mode(ProviderPageMode::Create);
}
fn edit_provider(&self, provider: Provider) {
let self_ = imp::ProvidersDialog::from_instance(self);
self_.deck.get().set_visible_child_name("provider");
self_.page.set_provider(provider);
self_.page.set_mode(ProviderPageMode::Edit);
}
}
mod row {
use super::*;
mod imp {
use super::*;
use glib::subclass;
use gtk::subclass::prelude::*;
use std::cell::RefCell;
static PROPERTIES: [subclass::Property; 1] = [subclass::Property("provider", |name| {
glib::ParamSpec::object(
name,
"Provider",
"The Provider",
Provider::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
)
})];
pub struct ProviderActionRow {
pub provider: RefCell<Option<Provider>>,
pub actions: gio::SimpleActionGroup,
}
impl ObjectSubclass for ProviderActionRow {
const NAME: &'static str = "ProviderActionRow";
type Type = super::ProviderActionRow;
type ParentType = libhandy::ActionRow;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn new() -> Self {
let actions = gio::SimpleActionGroup::new();
Self {
actions,
provider: RefCell::new(None),
}
}
fn class_init(klass: &mut Self::Class) {
klass.install_properties(&PROPERTIES);
}
}
impl ObjectImpl for ProviderActionRow {
fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("provider", ..) => {
let provider = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.provider.replace(provider);
}
_ => unimplemented!(),
}
}
fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("provider", ..) => self.provider.borrow().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
obj.setup_widgets();
self.parent_constructed(obj);
}
}
impl WidgetImpl for ProviderActionRow {}
impl ListBoxRowImpl for ProviderActionRow {}
impl libhandy::subclass::action_row::ActionRowImpl for ProviderActionRow {}
}
glib_wrapper! {
pub struct ProviderActionRow(ObjectSubclass<imp::ProviderActionRow>) @extends gtk::Widget, gtk::ListBoxRow, libhandy::ActionRow;
}
impl ProviderActionRow {
pub fn new(provider: Provider) -> Self {
glib::Object::new(Self::static_type(), &[("provider", &provider)])
.expect("Failed to create ProviderActionRow")
.downcast::<ProviderActionRow>()
.expect("Created object is of wrong type")
}
fn provider(&self) -> Provider {
let provider = self.get_property("provider").unwrap();
provider.get::<Provider>().unwrap().unwrap()
}
fn setup_widgets(&self) {
let self_ = imp::ProviderActionRow::from_instance(self);
self.provider()
.bind_property("name", self, "title")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
.build();
}
}
}

View file

@ -1,4 +1,8 @@
mod all;
mod list; mod list;
mod page;
mod row; mod row;
pub use self::all::ProvidersDialog;
pub use self::list::ProvidersList; pub use self::list::ProvidersList;
pub use self::page::{ProviderPage, ProviderPageMode};
pub use self::row::ProviderRow; pub use self::row::ProviderRow;

View file

@ -0,0 +1,156 @@
use crate::models::{Algorithm, Provider};
use gio::subclass::ObjectSubclass;
use glib::subclass::prelude::*;
use glib::translate::ToGlib;
use glib::{glib_object_subclass, glib_wrapper};
use gtk::{prelude::*, CompositeTemplate};
use libhandy::{ComboRowExt, EnumListModelExt};
pub enum ProviderPageMode {
Create,
Edit,
}
mod imp {
use super::*;
use glib::subclass;
use gtk::subclass::prelude::*;
#[derive(Debug, CompositeTemplate)]
pub struct ProviderPage {
#[template_child(id = "name_entry")]
pub name_entry: TemplateChild<gtk::Entry>,
#[template_child(id = "period_spinbutton")]
pub period_spinbutton: TemplateChild<gtk::SpinButton>,
#[template_child(id = "provider_website_entry")]
pub provider_website_entry: TemplateChild<gtk::Entry>,
#[template_child(id = "provider_help_entry")]
pub provider_help_entry: TemplateChild<gtk::Entry>,
#[template_child(id = "image_stack")]
pub image_stack: TemplateChild<gtk::Stack>,
#[template_child(id = "spinner")]
pub spinner: TemplateChild<gtk::Spinner>,
#[template_child(id = "algorithm_comborow")]
pub algorithm_comborow: TemplateChild<libhandy::ComboRow>,
#[template_child(id = "title")]
pub title: TemplateChild<gtk::Label>,
pub algorithms_model: libhandy::EnumListModel,
}
impl ObjectSubclass for ProviderPage {
const NAME: &'static str = "ProviderPage";
type Type = super::ProviderPage;
type ParentType = gtk::Box;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn new() -> Self {
let algorithms_model = libhandy::EnumListModel::new(Algorithm::static_type());
Self {
name_entry: TemplateChild::default(),
period_spinbutton: TemplateChild::default(),
provider_website_entry: TemplateChild::default(),
provider_help_entry: TemplateChild::default(),
image_stack: TemplateChild::default(),
spinner: TemplateChild::default(),
algorithm_comborow: TemplateChild::default(),
title: TemplateChild::default(),
algorithms_model,
}
}
fn class_init(klass: &mut Self::Class) {
klass.set_template_from_resource("/com/belmoussaoui/Authenticator/provider_page.ui");
Self::bind_template_children(klass);
}
}
impl ObjectImpl for ProviderPage {
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
obj.setup_widgets();
self.parent_constructed(obj);
}
}
impl WidgetImpl for ProviderPage {}
impl BoxImpl for ProviderPage {}
}
glib_wrapper! {
pub struct ProviderPage(ObjectSubclass<imp::ProviderPage>) @extends gtk::Widget, gtk::Box;
}
impl ProviderPage {
pub fn new() -> Self {
glib::Object::new(Self::static_type(), &[])
.expect("Failed to create ProviderPage")
.downcast::<ProviderPage>()
.expect("Created object is of wrong type")
}
pub fn set_provider(&self, provider: Provider) {
let self_ = imp::ProviderPage::from_instance(self);
self_.name_entry.get().set_text(&provider.name());
self_
.period_spinbutton
.get()
.set_value(provider.period() as f64);
if let Some(ref website) = provider.website() {
self_.provider_website_entry.get().set_text(website);
}
if let Some(ref website) = provider.help_url() {
self_.provider_help_entry.get().set_text(website);
}
self_.image_stack.get().set_visible_child_name("loading");
self_.spinner.get().start();
self_.algorithm_comborow.get().set_selected(
self_
.algorithms_model
.find_position(provider.algorithm().to_glib()),
);
/*let sender = self.sender.clone();
spawn!(async move {
if let Ok(file) = p.favicon().await {
send!(sender, AddAccountAction::SetIcon(file));
}
});*/
self_
.title
.get()
.set_text(&format!("Editing provider: {}", provider.name()));
}
fn setup_widgets(&self) {
let self_ = imp::ProviderPage::from_instance(self);
self_
.algorithm_comborow
.get()
.set_model(Some(&self_.algorithms_model));
}
pub fn set_mode(&self, mode: ProviderPageMode) {
let self_ = imp::ProviderPage::from_instance(self);
match mode {
ProviderPageMode::Create => {
self_.title.get().set_label("New Provider");
self_.name_entry.get().set_text("");
self_.period_spinbutton.get().set_value(30_f64);
self_.provider_website_entry.get().set_text("");
self_.provider_help_entry.get().set_text("");
self_.image_stack.get().set_visible_child_name("image");
self_.spinner.get().stop();
self_.algorithm_comborow.get().set_selected(0);
}
ProviderPageMode::Edit => {}
}
}
}

View file

@ -175,7 +175,6 @@ impl Window {
"gtk-application-prefer-dark-theme", "gtk-application-prefer-dark-theme",
gio::SettingsBindFlags::DEFAULT, gio::SettingsBindFlags::DEFAULT,
); );
self.set_default_size(360, 600);
} }
fn setup_actions(&self, app: &Application, sender: Sender<Action>) { fn setup_actions(&self, app: &Application, sender: Sender<Action>) {