implement records list builder, make some members public, drop extra getters

This commit is contained in:
yggverse 2024-11-18 11:44:33 +02:00
parent 38c3b01736
commit 6b5e712cdf
7 changed files with 96 additions and 94 deletions

View File

@ -28,6 +28,11 @@ package = "gtk4"
version = "0.9.1" version = "0.9.1"
features = ["v4_10"] features = ["v4_10"]
[dependencies.gio]
package = "gio"
version = "0.20.4"
features = ["v2_70"]
[dependencies.sqlite] [dependencies.sqlite]
package = "rusqlite" package = "rusqlite"
version = "0.32.1" version = "0.32.1"

View File

@ -2,11 +2,15 @@ mod widget;
use widget::Widget; use widget::Widget;
use crate::profile::Profile; use crate::profile::Profile;
use gtk::{glib::Uri, prelude::IsA}; use gtk::{
gio::{prelude::TlsCertificateExt, TlsCertificate},
glib::Uri,
prelude::IsA,
};
use std::rc::Rc; use std::rc::Rc;
pub struct Gemini { pub struct Gemini {
profile: Rc<Profile>, // profile: Rc<Profile>,
widget: Rc<Widget>, widget: Rc<Widget>,
} }
@ -15,8 +19,42 @@ impl Gemini {
/// Create new `Self` for given Profile /// Create new `Self` for given Profile
pub fn new(profile: Rc<Profile>, auth_uri: Uri) -> Self { pub fn new(profile: Rc<Profile>, auth_uri: Uri) -> Self {
// Init widget
let widget = Rc::new(Widget::new()); let widget = Rc::new(Widget::new());
// Add new identity option
widget.form.list.append(None, "Create new..");
// Collect additional options from database
match profile.identity.gemini.database.records() {
Ok(identities) => {
for identity in identities {
// Get certificate details
let certificate = match TlsCertificate::from_pem(&identity.pem) {
Ok(certificate) => certificate,
Err(reason) => todo!("{reason}"),
};
// Get expiration time
let expires = certificate
.not_valid_after()
.unwrap()
.format_iso8601()
.unwrap();
// Append record option
widget.form.list.append(
Some(identity.id),
&match identity.name {
Some(name) => format!("{name} ({expires})"),
None => format!("{expires}"),
},
);
}
}
Err(_) => todo!(),
}
// Init events // Init events
widget.connect_response({ widget.connect_response({
let profile = profile.clone(); let profile = profile.clone();
@ -33,7 +71,10 @@ impl Gemini {
}); });
// Return activated `Self` // Return activated `Self`
Self { profile, widget } Self {
// profile,
widget,
}
} }
// Actions // Actions

View File

@ -7,6 +7,7 @@ use adw::{
AlertDialog, ResponseAppearance, AlertDialog, ResponseAppearance,
}; };
use gtk::prelude::IsA; use gtk::prelude::IsA;
use std::rc::Rc;
// Defaults // Defaults
const HEADING: &str = "Ident"; const HEADING: &str = "Ident";
@ -17,13 +18,11 @@ const RESPONSE_APPLY: (&str, &str) = ("apply", "Apply");
const RESPONSE_CANCEL: (&str, &str) = ("cancel", "Cancel"); const RESPONSE_CANCEL: (&str, &str) = ("cancel", "Cancel");
// const RESPONSE_MANAGE: (&str, &str) = ("manage", "Manage"); // const RESPONSE_MANAGE: (&str, &str) = ("manage", "Manage");
// List options
const OPTION_CREATE: (Option<i64>, &str) = (None, "Create new..");
// Select options // Select options
pub struct Widget { pub struct Widget {
gobject: AlertDialog, pub form: Rc<Form>,
pub gobject: AlertDialog,
} }
impl Widget { impl Widget {
@ -31,12 +30,8 @@ impl Widget {
/// Create new `Self` /// Create new `Self`
pub fn new() -> Self { pub fn new() -> Self {
// Collect identity certificates
let mut options: Vec<(Option<i64>, String, bool)> = Vec::new();
options.push((OPTION_CREATE.0, OPTION_CREATE.1.to_owned(), false));
// Init child container // Init child container
let form = Form::new(options); let form = Rc::new(Form::new());
// Init main `GObject` // Init main `GObject`
let gobject = AlertDialog::builder() let gobject = AlertDialog::builder()
@ -44,7 +39,7 @@ impl Widget {
.body(BODY) .body(BODY)
.close_response(RESPONSE_CANCEL.0) .close_response(RESPONSE_CANCEL.0)
.default_response(RESPONSE_APPLY.0) .default_response(RESPONSE_APPLY.0)
.extra_child(form.gobject()) .extra_child(&form.gobject)
.build(); .build();
// Set response variants // Set response variants
@ -62,7 +57,7 @@ impl Widget {
gobject.set_response_appearance(RESPONSE_CANCEL.0, ResponseAppearance::Destructive); gobject.set_response_appearance(RESPONSE_CANCEL.0, ResponseAppearance::Destructive);
// Return new activated `Self` // Return new activated `Self`
Self { gobject } Self { form, gobject }
} }
// Actions // Actions

View File

@ -8,36 +8,37 @@ use gtk::{
prelude::{BoxExt, WidgetExt}, prelude::{BoxExt, WidgetExt},
Box, Orientation, Box, Orientation,
}; };
use std::rc::Rc;
pub struct Form { pub struct Form {
gobject: Box, pub gobject: Box,
pub list: Rc<List>,
// pub name: Rc<Name>,
} }
impl Form { impl Form {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn new(items: Vec<(Option<i64>, String, bool)>) -> Self { pub fn new() -> Self {
// Init components // Init components
let list = List::new(); let list = Rc::new(List::new());
let name = Name::new(); let name = Rc::new(Name::new());
// Init main container // Init main container
let gobject = Box::builder().orientation(Orientation::Vertical).build(); let gobject = Box::builder().orientation(Orientation::Vertical).build();
gobject.append(list.gobject()); gobject.append(&list.gobject);
gobject.append(name.gobject()); gobject.append(&name.gobject);
// Connect events // Connect events
list.on_select(move |key| name.gobject().set_visible(key.is_none())); list.connect_selected_notify(move |key| name.gobject.set_visible(key.is_none()));
// Return activated `Self` // Return activated `Self`
Self { gobject } Self {
gobject,
list,
// name,
} }
// Getters
pub fn gobject(&self) -> &Box {
&self.gobject
} }
} }

View File

@ -1,11 +1,10 @@
use gtk::{gio::ListStore, prelude::ObjectExt, DropDown, Label}; use gtk::{gio::ListStore, DropDown, Label};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
const PROPERTY_KEY_NAME: &str = "key"; // Store item key as GTK property
const PROPERTY_KEY_NONE_VALUE: i64 = -1; // C-type conversion for `None` values
pub struct List { pub struct List {
gobject: DropDown, pub gobject: DropDown,
model: ListStore, model: ListStore,
index: Rc<RefCell<HashMap<Label, Option<i64>>>>,
} }
impl List { impl List {
@ -13,74 +12,41 @@ impl List {
/// Create new `Self` /// Create new `Self`
pub fn new() -> Self { pub fn new() -> Self {
let index = Rc::new(RefCell::new(HashMap::new()));
let model = ListStore::new::<Label>(); let model = ListStore::new::<Label>();
let gobject = DropDown::builder().model(&model).build(); let gobject = DropDown::builder().model(&model).build();
Self { model, gobject } Self {
model,
index,
gobject,
}
} }
// Actions // Actions
/// Append new item with `profile_identity_gemini_id` as `key` and name as `value` /// Append new item with `profile_identity_gemini_id` as `key` and label as `value`
pub fn append(&self, key: Option<i64>, value: &str) { pub fn append(&self, profile_identity_gemini_id: Option<i64>, label: &str) {
// Create new label for item // Create new label for item
let item = Label::new(Some(value)); let item = Label::new(Some(label));
// Store key as property // Register ID in hash map index
item.set_property( self.index
PROPERTY_KEY_NAME, .borrow_mut()
match key { .insert(item.clone(), profile_identity_gemini_id);
Some(key) => key,
None => PROPERTY_KEY_NONE_VALUE,
},
);
// Set value as label
item.set_label(value);
// Append formatted record // Append formatted record
self.model.append(&item); self.model.append(&item);
} }
/* @TODO not in use
/// Get selected `key` or panic on selection not found
/// * return `None` if current selection key match `PROPERTY_KEY_NONE_VALUE`
pub fn selected(&self) -> Option<i64> {
selected(&self.gobject)
}*/
// Events // Events
/// Run callback function on `connect_selected_notify` /// Run callback function on `connect_selected_notify` event
/// * return formatted key as result /// * return formatted `profile_identity_gemini_id` match selected
pub fn on_select(&self, callback: impl Fn(Option<i64>) + 'static) { pub fn connect_selected_notify(&self, callback: impl Fn(Option<i64>) + 'static) {
self.gobject self.gobject.connect_selected_notify({
.connect_selected_notify(move |this| callback(selected(this))); let index = self.index.clone();
} move |list| callback(*index.borrow().get(&list.selected_item().unwrap()).unwrap())
});
// Getters
pub fn gobject(&self) -> &DropDown {
&self.gobject
}
}
// Tools
/// Get selected `key` or panic on selection not found
/// * return `None` if current selection key match `PROPERTY_KEY_NONE_VALUE`
fn selected(list: &DropDown) -> Option<i64> {
match list.selected_item() {
Some(this) => {
// Convert back from C-based GObject type
let key = this.property::<i64>(PROPERTY_KEY_NAME);
if key == PROPERTY_KEY_NONE_VALUE {
None
} else {
Some(key)
}
}
None => panic!(),
} }
} }

View File

@ -4,7 +4,7 @@ const PLACEHOLDER_TEXT: &str = "Identity name (optional)";
const MARGIN: i32 = 8; const MARGIN: i32 = 8;
pub struct Name { pub struct Name {
gobject: Entry, pub gobject: Entry,
} }
impl Name { impl Name {
@ -20,10 +20,4 @@ impl Name {
.build(), .build(),
} }
} }
// Getters
pub fn gobject(&self) -> &Entry {
&self.gobject
}
} }

View File

@ -7,7 +7,7 @@ pub struct Table {
pub id: i64, pub id: i64,
//pub profile_identity_id: i64, //pub profile_identity_id: i64,
pub pem: String, pub pem: String,
pub name: String, pub name: Option<String>,
} }
/// Storage for Gemini auth certificates /// Storage for Gemini auth certificates