gobjectify account & link it to a database

This commit is contained in:
Bilal Elmoussaoui 2020-10-29 04:59:12 +01:00
parent 06457fe63a
commit 73eafa58fd
17 changed files with 359 additions and 239 deletions

3
Cargo.lock generated
View file

@ -251,9 +251,6 @@ dependencies = [
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-xml 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.117 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.117 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
"surf 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"zbar-rust 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -16,9 +16,6 @@ lazy_static = "1.4"
log = "0.4"
pretty_env_logger = "0.4"
quick-xml = "0.20"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
surf = "2.1"
url = "2.1"
zbar-rust = "0.0"

View file

@ -1,7 +1,7 @@
-- Your SQL goes here
CREATE TABLE "accounts" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
"username" VARCHAR NOT NULL,
"name" VARCHAR NOT NULL,
"token_id" VARCHAR NOT NULL,
"provider_id" INTEGER NOT NULL
)

View file

@ -1,4 +1,5 @@
use crate::config;
use crate::models::Account;
use crate::widgets::{AddAccountDialog, View, Window};
use gio::prelude::*;
use gtk::prelude::*;
@ -9,6 +10,7 @@ use std::{cell::RefCell, rc::Rc};
use glib::{Receiver, Sender};
pub enum Action {
ViewAccounts,
AccountCreated(Account),
OpenAddAccountDialog,
}
@ -106,6 +108,9 @@ impl Application {
dialog.widget.show();
}
Action::ViewAccounts => self.window.set_view(View::Accounts),
Action::AccountCreated(account) => {
println!("{:#?}", account);
}
};
glib::Continue(true)

View file

@ -9,8 +9,6 @@ extern crate diesel;
#[macro_use]
extern crate glib;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate gtk_macros;
use gettextrs::*;

View file

@ -48,7 +48,6 @@ sources = files(
'models/database.rs',
'models/favicon.rs',
'models/mod.rs',
'models/object_wrapper.rs',
'models/provider.rs',
'models/providers.rs',
'widgets/accounts/add.rs',

View file

@ -1,40 +1,212 @@
use crate::models::database;
use crate::schema::accounts;
use anyhow::Result;
use diesel::RunQueryDsl;
use diesel::prelude::*;
#[derive(Queryable, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct Account {
pub id: i32,
pub username: String,
pub token_id: String,
pub provider: i32,
}
use glib::subclass;
use glib::subclass::prelude::*;
use glib::translate::*;
use glib::Cast;
use glib::{StaticType, ToValue};
use std::cell::{Cell, RefCell};
#[derive(Insertable)]
#[table_name = "accounts"]
pub struct NewAccount {
pub username: String,
struct NewAccount {
pub name: String,
pub token_id: String,
pub provider: i32,
pub provider_id: i32,
}
impl database::Insert<Account> for NewAccount {
type Error = anyhow::Error;
fn insert(&self) -> Result<Account> {
#[derive(Queryable, Hash, PartialEq, Eq, Debug, Clone)]
struct DiAccount {
pub id: i32,
pub name: String,
pub token_id: String,
pub provider_id: i32,
}
pub struct AccountPriv {
pub id: Cell<i32>,
pub name: RefCell<String>,
pub token_id: RefCell<String>,
pub provider_id: Cell<i32>,
}
static PROPERTIES: [subclass::Property; 4] = [
subclass::Property("id", |name| {
glib::ParamSpec::int(name, "id", "Id", 0, 1000, 0, glib::ParamFlags::READWRITE)
}),
subclass::Property("name", |name| {
glib::ParamSpec::string(name, "name", "Name", None, glib::ParamFlags::READWRITE)
}),
subclass::Property("token-id", |name| {
glib::ParamSpec::string(
name,
"token-id",
"token id",
None,
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("provider-id", |name| {
glib::ParamSpec::int(
name,
"provider-id",
"Provider Id",
0,
1000,
0,
glib::ParamFlags::READWRITE,
)
}),
];
impl ObjectSubclass for AccountPriv {
const NAME: &'static str = "Account";
type ParentType = glib::Object;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn class_init(klass: &mut Self::Class) {
klass.install_properties(&PROPERTIES);
}
fn new() -> Self {
Self {
id: Cell::new(0),
name: RefCell::new("".to_string()),
token_id: RefCell::new("".to_string()),
provider_id: Cell::new(0),
}
}
}
impl ObjectImpl for AccountPriv {
fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("id", ..) => {
let id = value
.get()
.expect("type conformity checked by `Object::set_property`")
.unwrap();
self.id.replace(id);
}
subclass::Property("name", ..) => {
let name = value
.get()
.expect("type conformity checked by `Object::set_property`")
.unwrap();
self.name.replace(name);
}
subclass::Property("token-id", ..) => {
let token_id = value
.get()
.expect("type conformity checked by `Object::set_property`")
.unwrap();
self.token_id.replace(token_id);
}
subclass::Property("provider-id", ..) => {
let provider_id = value
.get()
.expect("type conformity checked by `Object::set_property`")
.unwrap();
self.provider_id.replace(provider_id);
}
_ => unimplemented!(),
}
}
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("id", ..) => Ok(self.id.get().to_value()),
subclass::Property("name", ..) => Ok(self.name.borrow().to_value()),
subclass::Property("token-id", ..) => Ok(self.token_id.borrow().to_value()),
subclass::Property("provider-id", ..) => Ok(self.provider_id.get().to_value()),
_ => unimplemented!(),
}
}
}
glib_wrapper! {
pub struct Account(Object<subclass::simple::InstanceStruct<AccountPriv>, subclass::simple::ClassStruct<AccountPriv>, AccountClass>);
match fn {
get_type => || AccountPriv::get_type().to_glib(),
}
}
impl Account {
pub fn create(name: &str, token_id: &str, provider_id: i32) -> Result<Account> {
use diesel::{ExpressionMethods, QueryDsl};
let db = database::connection();
let conn = db.get()?;
diesel::insert_into(accounts::table)
.values(self)
.values(NewAccount {
name: name.to_string(),
token_id: token_id.to_string(),
provider_id,
})
.execute(&conn)?;
accounts::table
.order(accounts::columns::id.desc())
.first::<Account>(&conn)
.first::<DiAccount>(&conn)
.map_err(From::from)
.map(From::from)
}
pub fn load() -> Result<Vec<Self>> {
use crate::schema::accounts::dsl::*;
let db = database::connection();
let conn = db.get()?;
let results = accounts
.load::<DiAccount>(&conn)?
.into_iter()
.map(From::from)
.collect::<Vec<Account>>();
Ok(results)
}
pub fn new(id: i32, name: &str, token_id: &str, provider_id: i32) -> Account {
glib::Object::new(
Account::static_type(),
&[
("id", &id),
("name", &name),
("token-id", &token_id),
("provider-id", &provider_id),
],
)
.expect("Failed to create account")
.downcast()
.expect("Created account is of wrong type")
}
pub fn id(&self) -> i32 {
let priv_ = AccountPriv::from_instance(self);
priv_.id.get()
}
pub fn name(&self) -> String {
let priv_ = AccountPriv::from_instance(self);
priv_.name.borrow().clone()
}
}
impl From<DiAccount> for Account {
fn from(account: DiAccount) -> Self {
Self::new(
account.id,
&account.name,
&account.token_id,
account.provider_id,
)
}
}

View file

@ -1,6 +1,4 @@
use super::account::Account;
use super::database;
use super::object_wrapper::ObjectWrapper;
use super::provider::Provider;
use gio::prelude::*;
use std::cell::RefCell;
@ -36,7 +34,7 @@ pub struct AccountsModel {
impl AccountsModel {
pub fn from_provider(provider: &Provider) -> Self {
let gio_model = gio::ListStore::new(ObjectWrapper::static_type());
let gio_model = gio::ListStore::new(Account::static_type());
let model = Self {
model: gio_model,
sort_order: RefCell::new(SortOrder::Desc),
@ -49,18 +47,17 @@ impl AccountsModel {
fn init(&self) {
// fill in the accounts from the database
let accounts = database::get_accounts_by_provider(self.provider.clone()).unwrap();
/*let accounts = database::get_accounts_by_provider(self.provider.clone()).unwrap();
for account in accounts.into_iter() {
self.add_account(&account);
}
}*/
}
fn add_account(&self, account: &Account) {
let object = ObjectWrapper::new(Box::new(account));
let sort_by = self.sort_by.clone();
let sort_order = self.sort_order.clone();
self.model.insert_sorted(&object, move |a, b| {
self.model.insert_sorted(account, move |a, b| {
Self::accounts_cmp(a, b, sort_by.borrow().clone(), sort_order.borrow().clone())
});
}
@ -95,8 +92,8 @@ impl AccountsModel {
sort_by: SortBy,
sort_order: SortOrder,
) -> std::cmp::Ordering {
let mut account_a: Account = a.downcast_ref::<ObjectWrapper>().unwrap().deserialize();
let mut account_b: Account = b.downcast_ref::<ObjectWrapper>().unwrap().deserialize();
let mut account_a: &Account = a.downcast_ref::<Account>().unwrap();
let mut account_b: &Account = b.downcast_ref::<Account>().unwrap();
if sort_order == SortOrder::Desc {
let tmp = account_a;
@ -107,6 +104,6 @@ impl AccountsModel {
SortBy::Name => account_a.get_title().cmp(&account_b.get_title()),
SortBy::Date => account_a.get_created_at().cmp(&account_b.get_created_at()),
}*/
account_a.username.cmp(&account_b.username)
account_a.name().cmp(&account_b.name())
}
}

View file

@ -1,4 +1,3 @@
use crate::models::{Account, Provider};
use anyhow::Result;
use diesel::prelude::*;
use diesel::r2d2;
@ -47,14 +46,14 @@ pub trait Insert<T> {
fn insert(&self) -> Result<T, Self::Error>;
}
/*
pub fn get_accounts_by_provider(provider_model: Provider) -> Result<Vec<Account>> {
use crate::schema::accounts::dsl::*;
let db = connection();
let conn = db.get()?;
accounts
.filter(provider.eq(provider_model.id()))
.filter(provider_id.eq(provider_model.id()))
.load::<Account>(&conn)
.map_err(From::from)
}
@ -66,3 +65,4 @@ pub fn get_accounts() -> Result<Vec<Account>> {
accounts.load::<Account>(&conn).map_err(From::from)
}
*/

View file

@ -3,14 +3,12 @@ mod accounts;
mod algorithm;
pub mod database;
mod favicon;
mod object_wrapper;
mod provider;
mod providers;
pub use self::account::{Account, NewAccount};
pub use self::account::Account;
pub use self::accounts::AccountsModel;
pub use self::algorithm::Algorithm;
pub use self::favicon::{FaviconError, FaviconScrapper};
pub use self::object_wrapper::ObjectWrapper;
pub use self::provider::Provider;
pub use self::providers::ProvidersModel;

View file

@ -1,110 +0,0 @@
// ObjectWrapper is a GObject subclass, which we need to carry the rustio::Station/song::Song struct.
// With this we can use gtk::ListBox bind_model() properly.
//
// For more details, you should look at this gtk-rs example:
// https://github.com/gtk-rs/examples/blob/master/src/bin/listbox_model.rs
// Source https://gitlab.gnome.org/World/Shortwave/blob/master/src/model/object_wrapper.rs
use gtk::prelude::*;
use serde::de::DeserializeOwned;
use glib::subclass;
use glib::subclass::prelude::*;
use glib::translate::*;
mod imp {
use super::*;
use std::cell::RefCell;
pub struct ObjectWrapper {
data: RefCell<Option<String>>,
}
static PROPERTIES: [subclass::Property; 1] = [subclass::Property("data", |name| {
glib::ParamSpec::string(
name,
"Data",
"Data",
None, // Default value
glib::ParamFlags::READWRITE,
)
})];
impl ObjectSubclass for ObjectWrapper {
const NAME: &'static str = "ObjectWrapper";
type ParentType = glib::Object;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn class_init(klass: &mut Self::Class) {
klass.install_properties(&PROPERTIES);
}
fn new() -> Self {
Self {
data: RefCell::new(None),
}
}
}
impl ObjectImpl for ObjectWrapper {
fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("data", ..) => {
let data = value.get().unwrap();
self.data.replace(data);
}
_ => unimplemented!(),
}
}
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("data", ..) => Ok(self.data.borrow().to_value()),
_ => unimplemented!(),
}
}
}
}
glib_wrapper! {
pub struct ObjectWrapper(Object<subclass::simple::InstanceStruct<imp::ObjectWrapper>, subclass::simple::ClassStruct<imp::ObjectWrapper>, ObjectWrapperClass>);
match fn {
get_type => || imp::ObjectWrapper::get_type().to_glib(),
}
}
impl ObjectWrapper {
pub fn new<O>(object: O) -> ObjectWrapper
where
O: serde::ser::Serialize,
{
glib::Object::new(
Self::static_type(),
&[("data", &serde_json::to_string(&object).unwrap())],
)
.unwrap()
.downcast()
.unwrap()
}
pub fn deserialize<O>(&self) -> O
where
O: DeserializeOwned,
{
let data = self
.get_property("data")
.unwrap()
.get::<String>()
.unwrap()
.unwrap();
serde_json::from_str(&data).unwrap()
}
}

View file

@ -1,6 +1,7 @@
use super::algorithm::Algorithm;
use crate::models::database;
use crate::models::{FaviconError, FaviconScrapper};
use crate::schema::providers;
use anyhow::Result;
use diesel::RunQueryDsl;
use gio::FileExt;
@ -14,7 +15,18 @@ use std::str::FromStr;
use std::string::ToString;
use url::Url;
#[derive(Queryable, Hash, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
#[derive(Insertable)]
#[table_name = "providers"]
struct NewProvider {
pub name: String,
pub period: i32,
pub algorithm: String,
pub website: Option<String>,
pub help_url: Option<String>,
pub image_uri: Option<String>,
}
#[derive(Queryable, Hash, PartialEq, Eq, Debug, Clone)]
struct DiProvider {
pub id: i32,
pub name: String,
@ -194,6 +206,34 @@ glib_wrapper! {
}
impl Provider {
pub fn create(
name: &str,
period: i32,
algorithm: Algorithm,
website: Option<String>,
) -> Result<Self> {
use crate::diesel::{ExpressionMethods, QueryDsl};
let db = database::connection();
let conn = db.get()?;
diesel::insert_into(providers::table)
.values(NewProvider {
name: name.to_string(),
period,
algorithm: algorithm.to_string(),
website,
help_url: None,
image_uri: None,
})
.execute(&conn)?;
providers::table
.order(providers::columns::id.desc())
.first::<DiProvider>(&conn)
.map_err(From::from)
.map(From::from)
}
pub fn load() -> Result<Vec<Self>> {
use crate::schema::providers::dsl::*;
let db = database::connection();
@ -202,17 +242,7 @@ impl Provider {
let results = providers
.load::<DiProvider>(&conn)?
.into_iter()
.map(|p| {
Self::new(
p.id,
&p.name,
p.period,
Algorithm::from_str(&p.algorithm).unwrap(),
p.website,
p.help_url,
p.image_uri,
)
})
.map(From::from)
.collect::<Vec<Provider>>();
Ok(results)
}
@ -304,3 +334,17 @@ impl Provider {
priv_.image_uri.borrow().clone()
}
}
impl From<DiProvider> for Provider {
fn from(p: DiProvider) -> Self {
Self::new(
p.id,
&p.name,
p.period,
Algorithm::from_str(&p.algorithm).unwrap(),
p.website,
p.help_url,
p.image_uri,
)
}
}

View file

@ -1,9 +1,9 @@
table! {
accounts (id) {
id -> Integer,
username -> Text,
name -> Text,
token_id -> Text,
provider -> Integer,
provider_id -> Integer,
}
}

View file

@ -1,9 +1,7 @@
use crate::application::Action;
use crate::helpers::qrcode;
use crate::models::database::*;
use crate::models::{Account, Algorithm, NewAccount, Provider, ProvidersModel};
use crate::models::{Account, Algorithm, Provider, ProvidersModel};
use anyhow::Result;
use futures::future::FutureExt;
use gio::prelude::*;
use glib::StaticType;
use glib::{signal::Inhibit, Receiver, Sender};
@ -14,6 +12,8 @@ use std::rc::Rc;
pub enum AddAccountAction {
SetIcon(gio::File),
Save,
ScanQR,
}
pub struct AddAccountDialog {
@ -24,6 +24,7 @@ pub struct AddAccountDialog {
receiver: RefCell<Option<Receiver<AddAccountAction>>>,
model: Rc<ProvidersModel>,
selected_provider: Rc<RefCell<Option<Provider>>>,
actions: gio::SimpleActionGroup,
}
impl AddAccountDialog {
@ -33,6 +34,7 @@ impl AddAccountDialog {
let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let receiver = RefCell::new(Some(r));
let actions = gio::SimpleActionGroup::new();
let add_account_dialog = Rc::new(Self {
widget: add_dialog,
@ -40,40 +42,88 @@ impl AddAccountDialog {
global_sender,
sender,
receiver,
actions,
model: Rc::new(ProvidersModel::new()),
selected_provider: Rc::new(RefCell::new(None)),
});
add_account_dialog.setup_actions(add_account_dialog.clone());
add_account_dialog.setup_actions();
add_account_dialog.setup_signals();
add_account_dialog.setup_widgets(add_account_dialog.clone());
add_account_dialog
}
fn add_account(&self, account: NewAccount) -> Result<Account> {
// TODO: add the account to the provider model.
account.insert()
}
fn setup_signals(&self) {
get_widget!(self.builder, gtk::Entry, username_entry);
get_widget!(self.builder, gtk::Entry, token_entry);
//let action_group = self.widget.get_action_group("add").unwrap().downcast::<gio::SimpleActionGroup>().unwrap();
//let save_action = action_group.lookup_action("save").unwrap().downcast::<gio::SimpleAction>().unwrap();
let validate_entries = clone!(@weak username_entry, @weak token_entry => move |_: &gtk::Entry| {
let validate_entries = clone!(@weak username_entry, @weak token_entry, @strong self.actions as actions => move |_: &gtk::Entry| {
let username = username_entry.get_text().unwrap();
let token = token_entry.get_text().unwrap();
let is_valid = !(username.is_empty() || token.is_empty());
//save_action.set_enabled(is_valid);
get_action!(actions, @save).set_enabled(is_valid);
});
username_entry.connect_changed(validate_entries.clone());
token_entry.connect_changed(validate_entries);
}
fn scan_qr(&self) {
qrcode::screenshot_area(
clone!(@strong self.builder as builder, @strong self.model as model => move |screenshot| {
if let Ok(otpauth) = qrcode::scan(&gio::File::new_for_uri(&screenshot)) {
get_widget!(builder, gtk::Entry, @token_entry).set_text(&otpauth.token);
if let Some(ref username) = otpauth.account {
get_widget!(builder, gtk::Entry, @username_entry).set_text(&username);
}
if let Some(ref provider) = otpauth.issuer {
let provider = model.find_by_name(provider).unwrap();
//dialog.set_provider(provider);
}
}
}),
);
}
fn save(&self) -> Result<()> {
let provider = match self.selected_provider.borrow().clone() {
Some(p) => p,
None => {
let provider_website =
get_widget!(self.builder, gtk::Entry, @provider_website_entry).get_text();
let provider_name = get_widget!(self.builder, gtk::Entry, @provider_entry)
.get_text()
.unwrap();
let period = get_widget!(self.builder, gtk::SpinButton, @period_spinbutton)
.get_value() as i32;
let selected_alg =
get_widget!(self.builder, libhandy::ComboRow, @algorithm_comborow)
.get_selected();
let algorithm: Algorithm = unsafe { std::mem::transmute(selected_alg) };
Provider::create(
&provider_name,
period,
algorithm,
provider_website.map(|w| w.to_string()),
)
.unwrap()
}
};
let username = get_widget!(self.builder, gtk::Entry, @username_entry)
.get_text()
.unwrap();
let token = get_widget!(self.builder, gtk::Entry, @token_entry)
.get_text()
.unwrap();
let account = Account::create(&username, &token, provider.id())?;
send!(self.global_sender, Action::AccountCreated(account));
Ok(())
}
fn set_provider(&self, provider: Provider) {
get_widget!(self.builder, gtk::Entry, @provider_entry).set_text(&provider.name());
get_widget!(self.builder, gtk::SpinButton, @period_spinbutton)
@ -106,61 +156,32 @@ impl AddAccountDialog {
self.selected_provider.replace(Some(provider));
}
fn setup_actions(&self, dialog: Rc<Self>) {
let actions = gio::SimpleActionGroup::new();
fn setup_actions(&self) {
action!(
actions,
self.actions,
"back",
clone!(@weak self.widget as dialog => move |_, _| {
dialog.destroy();
})
);
let save = gio::SimpleAction::new("save", None);
save.connect_activate(clone!(@strong self.builder as builder => move |_, _| {
get_widget!(builder, gtk::Entry, username_entry);
get_widget!(builder, gtk::Entry, token_entry);
get_widget!(builder, gtk::Entry, provider_entry);
get_widget!(builder, gtk::Entry, website_entry);
// get_widget!(builder, gtk::Entry, period_entry);
// get_widget!(builder, gtk::Entry, algorithm_model);
/*
let new_account = NewAccount {
username: username_entry.get_text().unwrap().to_string(),
token_id: token_entry.get_text().unwrap().to_string(),
provider: provider_combobox.get_active_id().unwrap().parse::<i32>().unwrap(),
};
if let Err(err) = add_account_dialog.add_account(new_account) {
add_account_dialog.notify_err("Failed to add a new account");
} else {
// Close the dialog if everything is fine.
add_account_dialog.widget.destroy();
}
*/
}));
save.set_enabled(false);
actions.add_action(&save);
action!(
actions,
"scan-qr",
clone!(@strong self.builder as builder, @strong dialog, @strong self.model as model => move |_, _| {
qrcode::screenshot_area(clone!(@strong builder, @strong dialog, @strong model => move |screenshot| {
if let Ok(otpauth) = qrcode::scan(&gio::File::new_for_uri(&screenshot)) {
get_widget!(builder, gtk::Entry, @token_entry).set_text(&otpauth.token);
if let Some(ref username) = otpauth.account {
get_widget!(builder, gtk::Entry, @username_entry).set_text(&username);
}
if let Some(ref provider) = otpauth.issuer {
let provider = model.find_by_name(provider). unwrap();
dialog.set_provider(provider);
}
}
}));
self.actions,
"save",
clone!(@strong self.sender as sender => move |_, _| {
send!(sender, AddAccountAction::Save);
})
);
self.widget.insert_action_group("add", Some(&actions));
action!(
self.actions,
"scan-qr",
clone!(@strong self.sender as sender => move |_, _| {
send!(sender, AddAccountAction::ScanQR);
})
);
self.widget.insert_action_group("add", Some(&self.actions));
get_action!(self.actions, @save).set_enabled(false);
}
fn setup_widgets(&self, dialog: Rc<Self>) {
@ -209,6 +230,10 @@ impl AddAccountDialog {
get_widget!(self.builder, gtk::Spinner, @spinner).stop();
get_widget!(self.builder, gtk::Stack, @image_stack).set_visible_child_name("image");
}
AddAccountAction::Save => {
self.save().unwrap();
}
AddAccountAction::ScanQR => self.scan_qr(),
};
glib::Continue(true)
}

View file

@ -3,7 +3,7 @@ use gtk::prelude::*;
use glib::Sender;
use crate::application::Action;
use crate::models::{Account, AccountsModel, ObjectWrapper, Provider};
use crate::models::{Account, AccountsModel, Provider};
use crate::widgets::accounts::AccountRow;
pub struct AccountsList<'a> {
@ -43,10 +43,9 @@ impl<'a> AccountsList<'a> {
Some(&self.model.model),
Some(Box::new(
clone!(@strong self.sender as sender => move |account: &glib::Object| {
let account: Account = account
.downcast_ref::<ObjectWrapper>()
.unwrap()
.deserialize();
let account: &Account = account
.downcast_ref::<Account>()
.unwrap();
let row = AccountRow::new(account, sender.clone());
/*row.set_on_click_callback(move |_, _| {
// sender.send(Action::LoadChapter(chapter.clone())).unwrap();

View file

@ -3,15 +3,15 @@ use crate::models::Account;
use glib::Sender;
use gtk::prelude::*;
pub struct AccountRow {
pub struct AccountRow<'a> {
pub widget: gtk::ListBoxRow,
builder: gtk::Builder,
sender: Sender<Action>,
account: Account,
account: &'a Account,
}
impl AccountRow {
pub fn new(account: Account, sender: Sender<Action>) -> Self {
impl<'a> AccountRow<'a> {
pub fn new(account: &'a Account, sender: Sender<Action>) -> Self {
let builder = gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/account_row.ui");
get_widget!(builder, gtk::ListBoxRow, account_row);
@ -27,6 +27,6 @@ impl AccountRow {
fn init(&self) {
get_widget!(self.builder, gtk::Label, username_label);
username_label.set_text(&self.account.username);
username_label.set_text(&self.account.name());
}
}

View file

@ -4,7 +4,6 @@ use std::cell::RefCell;
use crate::application::Action;
use crate::models::ProvidersModel;
use crate::widgets::AccountsList;
pub struct ProvidersList {
pub widget: gtk::Box,