client: accounts: Add support for Thunderbird autoconfig

- Auto detect server settings
- Rework accounts editor add panel

Fix #1390
Fix #1350
This commit is contained in:
Cédric Bellegarde 2022-08-25 23:12:45 +02:00
parent 14d5a4f510
commit a14f5d4799
7 changed files with 507 additions and 363 deletions

View file

@ -93,6 +93,7 @@ libhandy = dependency('libhandy-1', version: '>= 1.2.1', required: false)
libmath = cc.find_library('m') libmath = cc.find_library('m')
libpeas = dependency('libpeas-1.0', version: '>= 1.24.0') libpeas = dependency('libpeas-1.0', version: '>= 1.24.0')
libsecret = dependency('libsecret-1', version: '>= 0.11') libsecret = dependency('libsecret-1', version: '>= 0.11')
libsoup = dependency('libsoup-3.0')
libstemmer_dep = cc.find_library('stemmer') libstemmer_dep = cc.find_library('stemmer')
libunwind_dep = dependency( libunwind_dep = dependency(
'libunwind', version: '>= 1.1', required: get_option('libunwind') 'libunwind', version: '>= 1.1', required: get_option('libunwind')

View file

@ -0,0 +1,170 @@
/*
* Copyright 2022 Cédric Bellegarde <cedric.bellegarde@adishatz.org>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* Thunderbird autoconfig XML values
*/
internal class Accounts.AutoConfigValues {
// emailProvider.id
public string id { get; set; default = ""; }
// incomingServer[type="imap"].hostname
public string imap_server { get; set; default = ""; }
// incomingServer[type="imap"].port
public string imap_port { get; set; default = ""; }
// incomingServer[type="imap"].socketType
public Geary.TlsNegotiationMethod imap_tls_method {
get; set; default = Geary.TlsNegotiationMethod.TRANSPORT;
}
// outgoingServer[type="smtp"].hostname
public string smtp_server{ get; set; default = ""; }
// outgoingServer[type="smtp"].port
public string smtp_port { get; set; default = ""; }
// outgoingServer[type="smtp"].socketType
public Geary.TlsNegotiationMethod smtp_tls_method {
get; set; default = Geary.TlsNegotiationMethod.TRANSPORT;
}
}
internal errordomain Accounts.AutoConfigError {
ERROR
}
/**
* An account autoconfiguration helper
*/
internal class Accounts.AutoConfig {
private static string AUTOCONFIG_BASE_URI = "https://autoconfig.thunderbird.net/v1.1/";
private static string AUTOCONFIG_PATH = "/mail/config-v1.1.xml";
private unowned GLib.Cancellable cancellable;
internal AutoConfig(GLib.Cancellable auto_config_cancellable) {
cancellable = auto_config_cancellable;
}
public async AutoConfigValues get_config(string hostname)
throws AutoConfigError {
AutoConfigValues auto_config_values;
// First try to get config from mail domain, then from thunderbird
try {
auto_config_values = yield get_config_for_uri(
"https://autoconfig." + hostname + AUTOCONFIG_PATH
);
} catch (AutoConfigError err) {
auto_config_values = yield get_config_for_uri(
AUTOCONFIG_BASE_URI + hostname
);
}
return auto_config_values;
}
private async AutoConfigValues get_config_for_uri(string uri)
throws AutoConfigError {
GLib.InputStream stream;
var session = new Soup.Session();
var msg = new Soup.Message("GET", uri);
try {
stream = yield session.send_async(
msg, Priority.DEFAULT, this.cancellable
);
} catch (GLib.Error err) {
throw new AutoConfigError.ERROR(err.message);
}
try {
var stdout_stream = new MemoryOutputStream.resizable();
yield stdout_stream.splice_async(
stream, 0, Priority.DEFAULT, null
);
stdout_stream.write("\0".data);
stdout_stream.close();
unowned var xml_data = (string) stdout_stream.get_data();
return get_config_for_xml(xml_data);
} catch (GLib.Error err) {
throw new AutoConfigError.ERROR(err.message);
} finally {
try {
yield stream.close_async();
} catch (GLib.Error err) {
// Oh well
}
}
}
private AutoConfigValues get_config_for_xml(string xml_data)
throws AutoConfigError {
unowned Xml.Doc doc = Xml.Parser.parse_memory(xml_data, xml_data.length);
if (doc == null) {
throw new AutoConfigError.ERROR("Invalid XML");
}
unowned Xml.Node root = doc.get_root_element();
unowned Xml.Node email_provider = get_node(root, "emailProvider");
unowned Xml.Node incoming_server = get_node(email_provider, "incomingServer");
unowned Xml.Node outgoing_server = get_node(email_provider, "outgoingServer");
if (incoming_server == null || outgoing_server == null) {
throw new AutoConfigError.ERROR("Invalid XML");
}
if (incoming_server.get_prop("type") != "imap" ||
outgoing_server.get_prop("type") != "smtp") {
throw new AutoConfigError.ERROR("Unsupported protocol");
}
var auto_config_values = new AutoConfigValues();
auto_config_values.id = email_provider.get_prop("id");
auto_config_values.imap_server = get_node_value(incoming_server, "hostname");
auto_config_values.imap_port = get_node_value(incoming_server, "port");
auto_config_values.imap_tls_method = get_tls_method(
get_node_value(incoming_server, "socketType")
);
auto_config_values.smtp_server = get_node_value(outgoing_server, "hostname");
auto_config_values.smtp_port = get_node_value(outgoing_server, "port");
auto_config_values.smtp_tls_method = get_tls_method(
get_node_value(outgoing_server, "socketType")
);
return auto_config_values;
}
private unowned Xml.Node? get_node(Xml.Node root, string name) {
for (unowned Xml.Node entry = root.children; entry != null; entry = entry.next) {
if (entry.type == Xml.ElementType.ELEMENT_NODE && entry.name == name) {
return entry;
}
}
return null;
}
private string get_node_value(Xml.Node root, string name) {
unowned Xml.Node? node = get_node(root, name);
if (node == null)
return "";
return node.get_content();
}
private Geary.TlsNegotiationMethod get_tls_method(string method) {
switch (method) {
case "SSL":
return Geary.TlsNegotiationMethod.TRANSPORT;
case "STARTTLS":
return Geary.TlsNegotiationMethod.START_TLS;
default:
return Geary.TlsNegotiationMethod.NONE;
}
}
}

View file

@ -37,31 +37,29 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
[GtkChild] private unowned Gtk.HeaderBar header; [GtkChild] private unowned Gtk.HeaderBar header;
[GtkChild] private unowned Gtk.Grid pane_content; [GtkChild] private unowned Gtk.Stack stack;
[GtkChild] private unowned Gtk.Adjustment pane_adjustment; [GtkChild] private unowned Gtk.Adjustment pane_adjustment;
[GtkChild] private unowned Gtk.ListBox details_list; [GtkChild] private unowned Gtk.ListBox details_list;
[GtkChild] private unowned Gtk.Grid receiving_panel;
[GtkChild] private unowned Gtk.ListBox receiving_list; [GtkChild] private unowned Gtk.ListBox receiving_list;
[GtkChild] private unowned Gtk.Grid sending_panel;
[GtkChild] private unowned Gtk.ListBox sending_list; [GtkChild] private unowned Gtk.ListBox sending_list;
[GtkChild] private unowned Gtk.Button create_button; [GtkChild] private unowned Gtk.Button action_button;
[GtkChild] private unowned Gtk.Button back_button; [GtkChild] private unowned Gtk.Button back_button;
[GtkChild] private unowned Gtk.Spinner create_spinner; [GtkChild] private unowned Gtk.Spinner action_spinner;
private NameRow real_name; private NameRow real_name;
private EmailRow email = new EmailRow(); private EmailRow email = new EmailRow();
private string last_valid_email = ""; private string last_valid_email = "";
private string last_valid_hostname = ""; private string last_valid_hostname = "";
private GLib.Cancellable auto_config_cancellable = new GLib.Cancellable();
private HostnameRow imap_hostname = new HostnameRow(Geary.Protocol.IMAP); private HostnameRow imap_hostname = new HostnameRow(Geary.Protocol.IMAP);
private TransportSecurityRow imap_tls = new TransportSecurityRow(); private TransportSecurityRow imap_tls = new TransportSecurityRow();
private LoginRow imap_login = new LoginRow(); private LoginRow imap_login = new LoginRow();
@ -76,32 +74,19 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
private bool controls_valid = false; private bool controls_valid = false;
internal EditorAddPane(Editor editor, Geary.ServiceProvider provider) { internal EditorAddPane(Editor editor) {
this.editor = editor; this.editor = editor;
this.provider = provider; this.provider = Geary.ServiceProvider.OTHER;
this.accounts = editor.application.controller.account_manager; this.accounts = editor.application.controller.account_manager;
this.engine = editor.application.engine; this.engine = editor.application.engine;
this.pane_content.set_focus_vadjustment(this.pane_adjustment); this.stack.set_focus_vadjustment(this.pane_adjustment);
this.details_list.set_header_func(Editor.seperator_headers); this.details_list.set_header_func(Editor.seperator_headers);
this.receiving_list.set_header_func(Editor.seperator_headers); this.receiving_list.set_header_func(Editor.seperator_headers);
this.sending_list.set_header_func(Editor.seperator_headers); this.sending_list.set_header_func(Editor.seperator_headers);
if (provider != Geary.ServiceProvider.OTHER) {
this.details_list.add(
new ServiceProviderRow<EditorAddPane>(
provider,
// Translators: Label for adding an email account
// account for a generic IMAP service provider.
_("All others")
)
);
this.receiving_panel.hide();
this.sending_panel.hide();
}
this.real_name = new NameRow(this.accounts.get_account_name()); this.real_name = new NameRow(this.accounts.get_account_name());
this.details_list.add(this.real_name); this.details_list.add(this.real_name);
@ -130,18 +115,14 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
this.smtp_password.validator.state_changed.connect(on_validated); this.smtp_password.validator.state_changed.connect(on_validated);
this.smtp_password.value.activate.connect(on_activated); this.smtp_password.value.activate.connect(on_activated);
if (provider == Geary.ServiceProvider.OTHER) { this.receiving_list.add(this.imap_hostname);
this.receiving_list.add(this.imap_hostname); this.receiving_list.add(this.imap_tls);
this.receiving_list.add(this.imap_tls); this.receiving_list.add(this.imap_login);
this.receiving_list.add(this.imap_login); this.receiving_list.add(this.imap_password);
this.receiving_list.add(this.imap_password);
this.sending_list.add(this.smtp_hostname); this.sending_list.add(this.smtp_hostname);
this.sending_list.add(this.smtp_tls); this.sending_list.add(this.smtp_tls);
this.sending_list.add(this.smtp_auth); this.sending_list.add(this.smtp_auth);
} else {
this.details_list.add(this.imap_password);
}
} }
internal Gtk.HeaderBar get_header() { internal Gtk.HeaderBar get_header() {
@ -169,7 +150,8 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
account.outgoing = new_smtp_service(); account.outgoing = new_smtp_service();
account.untrusted_host.connect(on_untrusted_host); account.untrusted_host.connect(on_untrusted_host);
if (this.provider == Geary.ServiceProvider.OTHER) { if (this.provider == Geary.ServiceProvider.OTHER &&
this.imap_hostname.get_visible()) {
bool imap_valid = false; bool imap_valid = false;
bool smtp_valid = false; bool smtp_valid = false;
@ -302,30 +284,22 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
Geary.Protocol.IMAP, this.provider Geary.Protocol.IMAP, this.provider
); );
if (this.provider == Geary.ServiceProvider.OTHER) { service.credentials = new Geary.Credentials(
service.credentials = new Geary.Credentials( Geary.Credentials.Method.PASSWORD,
Geary.Credentials.Method.PASSWORD, this.imap_login.value.get_text().strip(),
this.imap_login.value.get_text().strip(), this.imap_password.value.get_text().strip()
this.imap_password.value.get_text().strip() );
);
Components.NetworkAddressValidator host = Components.NetworkAddressValidator host =
(Components.NetworkAddressValidator) (Components.NetworkAddressValidator)
this.imap_hostname.validator; this.imap_hostname.validator;
GLib.NetworkAddress address = host.validated_address; GLib.NetworkAddress address = host.validated_address;
service.host = address.hostname; service.host = address.hostname;
service.port = (uint16) address.port; service.port = (uint16) address.port;
service.transport_security = this.imap_tls.value.method; service.transport_security = this.imap_tls.value.method;
if (service.port == 0) { if (service.port == 0) {
service.port = service.get_default_port(); service.port = service.get_default_port();
}
} else {
service.credentials = new Geary.Credentials(
Geary.Credentials.Method.PASSWORD,
this.email.value.get_text().strip(),
this.imap_password.value.get_text().strip()
);
} }
return service; return service;
@ -336,96 +310,121 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
Geary.Protocol.SMTP, this.provider Geary.Protocol.SMTP, this.provider
); );
if (this.provider == Geary.ServiceProvider.OTHER) { service.credentials_requirement = this.smtp_auth.value.source;
service.credentials_requirement = this.smtp_auth.value.source; if (service.credentials_requirement ==
if (service.credentials_requirement == Geary.Credentials.Requirement.CUSTOM) {
Geary.Credentials.Requirement.CUSTOM) { service.credentials = new Geary.Credentials(
service.credentials = new Geary.Credentials( Geary.Credentials.Method.PASSWORD,
Geary.Credentials.Method.PASSWORD, this.smtp_login.value.get_text().strip(),
this.smtp_login.value.get_text().strip(), this.smtp_password.value.get_text().strip()
this.smtp_password.value.get_text().strip() );
); }
}
Components.NetworkAddressValidator host = Components.NetworkAddressValidator host =
(Components.NetworkAddressValidator) (Components.NetworkAddressValidator)
this.smtp_hostname.validator; this.smtp_hostname.validator;
GLib.NetworkAddress address = host.validated_address; GLib.NetworkAddress address = host.validated_address;
service.host = address.hostname; service.host = address.hostname;
service.port = (uint16) address.port; service.port = (uint16) address.port;
service.transport_security = this.smtp_tls.value.method; service.transport_security = this.smtp_tls.value.method;
if (service.port == 0) { if (service.port == 0) {
service.port = service.get_default_port(); service.port = service.get_default_port();
}
} }
return service; return service;
} }
private void check_validation() { private void check_validation() {
bool server_settings_visible = this.stack.get_visible_child_name() == "server_settings";
bool controls_valid = true; bool controls_valid = true;
foreach (Gtk.ListBox list in new Gtk.ListBox[] { Gtk.ListBox[] list_boxes;
if (server_settings_visible) {
list_boxes = new Gtk.ListBox[] {
this.details_list, this.receiving_list, this.sending_list this.details_list, this.receiving_list, this.sending_list
}) { };
list.foreach((child) => { } else {
list_boxes = new Gtk.ListBox[] { this.details_list };
}
foreach (Gtk.ListBox list_box in list_boxes) {
list_box.foreach((child) => {
AddPaneRow? validatable = child as AddPaneRow; AddPaneRow? validatable = child as AddPaneRow;
if (validatable != null && !validatable.validator.is_valid) { if (validatable != null && !validatable.validator.is_valid) {
controls_valid = false; controls_valid = false;
} }
}); });
} }
this.create_button.set_sensitive(controls_valid); this.action_button.set_sensitive(controls_valid);
this.controls_valid = controls_valid; this.controls_valid = controls_valid;
} }
private void update_operation_ui(bool is_running) { private void update_operation_ui(bool is_running) {
this.create_spinner.visible = is_running; this.action_spinner.visible = is_running;
this.create_spinner.active = is_running; this.action_spinner.active = is_running;
this.create_button.sensitive = !is_running; this.action_button.sensitive = !is_running;
this.back_button.sensitive = !is_running; this.back_button.sensitive = !is_running;
this.sensitive = !is_running; this.sensitive = !is_running;
} }
private void on_validated(Components.Validator.Trigger reason) { private void switch_to_user_settings() {
check_validation(); this.stack.set_visible_child_name("user_settings");
if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) { this.action_button.set_label(_("_Next"));
this.create_button.clicked(); this.action_button.set_sensitive(true);
} this.action_button.get_style_context().remove_class("suggested-action");
} }
private void on_activated() { private void switch_to_server_settings() {
if (this.controls_valid) { this.stack.set_visible_child_name("server_settings");
this.create_button.clicked(); this.action_button.set_label(_("_Create"));
} this.action_button.set_sensitive(false);
this.action_button.get_style_context().add_class("suggested-action");
} }
private void on_email_changed() { private void set_server_settings_from_autoconfig(AutoConfig auto_config,
Gtk.Entry imap_login_entry = this.imap_login.value; GLib.AsyncResult res)
Gtk.Entry smtp_login_entry = this.smtp_login.value; throws Accounts.AutoConfigError {
AutoConfigValues auto_config_values = auto_config.get_config.end(res);
Gtk.Entry imap_hostname_entry = this.imap_hostname.value; Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value; Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
string email = ""; TlsComboBox imap_tls_combo_box = this.imap_tls.value;
string hostname = ""; TlsComboBox smtp_tls_combo_box = this.smtp_tls.value;
string imap_hostname = "";
string smtp_hostname = ""; imap_hostname_entry.text = auto_config_values.imap_server +
":" + auto_config_values.imap_port;
smtp_hostname_entry.text = auto_config_values.smtp_server +
":" + auto_config_values.smtp_port;
imap_tls_combo_box.method = auto_config_values.imap_tls_method;
smtp_tls_combo_box.method = auto_config_values.smtp_tls_method;
this.imap_hostname.hide();
this.smtp_hostname.hide();
this.imap_tls.hide();
this.smtp_tls.hide();
switch (auto_config_values.id) {
case "googlemail.com":
this.provider = Geary.ServiceProvider.GMAIL;
break;
case "hotmail.com":
this.provider = Geary.ServiceProvider.OUTLOOK;
break;
default:
this.provider = Geary.ServiceProvider.OTHER;
break;
}
}
private void set_server_settings_from_hostname(string hostname) {
Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
string smtp_hostname = "smtp." + hostname;
string imap_hostname = "imap." + hostname;
string last_imap_hostname = ""; string last_imap_hostname = "";
string last_smtp_hostname = ""; string last_smtp_hostname = "";
if (this.email.validator.state == Components.Validator.Validity.VALID) { this.imap_hostname.show();
email = this.email.value.text; this.smtp_hostname.show();
hostname = email.split("@")[1];
smtp_hostname = "smtp." + hostname;
imap_hostname = "imap." + hostname;
}
if (imap_login_entry.text == this.last_valid_email) {
imap_login_entry.text = email;
}
if (smtp_login_entry.text == this.last_valid_email) {
smtp_login_entry.text = email;
}
if (this.last_valid_hostname != "") { if (this.last_valid_hostname != "") {
last_imap_hostname = "imap." + this.last_valid_hostname; last_imap_hostname = "imap." + this.last_valid_hostname;
@ -437,9 +436,102 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
if (smtp_hostname_entry.text == last_smtp_hostname) { if (smtp_hostname_entry.text == last_smtp_hostname) {
smtp_hostname_entry.text = smtp_hostname; smtp_hostname_entry.text = smtp_hostname;
} }
this.last_valid_hostname = hostname;
}
private void add_goa_account() {
this.accounts.add_goa_account.begin(
this.provider, this.op_cancellable,
(obj, res) => {
bool add_local = false;
try {
this.accounts.add_goa_account.end(res);
} catch (GLib.IOError.NOT_SUPPORTED err) {
// Not a supported type, so don't bother logging the error
add_local = true;
} catch (GLib.Error err) {
debug("Failed to add %s via GOA: %s",
this.provider.to_string(), err.message);
add_local = true;
}
// Google Mail does not support "Less secure apps" anymore
if (add_local) {
switch (this.provider) {
case Geary.ServiceProvider.GMAIL:
this.editor.add_notification(
new Components.InAppNotification(
// Translators: In-app notification label, when
// GNOME Online Accounts are missing
_("Online accounts are missing")
)
);
add_local = false;
break;
default:
break;
}
}
if (add_local) {
switch_to_server_settings();
} else {
this.editor.pop();
}
}
);
}
private void on_validated(Components.Validator.Trigger reason) {
check_validation();
if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) {
this.action_button.clicked();
}
}
private void on_activated() {
if (this.controls_valid) {
this.action_button.clicked();
}
}
private void on_email_changed() {
Gtk.Entry imap_login_entry = this.imap_login.value;
Gtk.Entry smtp_login_entry = this.smtp_login.value;
this.auto_config_cancellable.cancel();
if (this.email.validator.state != Components.Validator.Validity.VALID) {
return;
}
string email = this.email.value.text;
string hostname = email.split("@")[1];
// Do not update entries if changed by user
if (imap_login_entry.text == this.last_valid_email) {
imap_login_entry.text = email;
}
if (smtp_login_entry.text == this.last_valid_email) {
smtp_login_entry.text = email;
}
this.last_valid_email = email; this.last_valid_email = email;
this.last_valid_hostname = hostname;
// Try to get configuration from Thunderbird autoconfig service
this.action_spinner.visible = true;
this.action_spinner.active = true;
this.auto_config_cancellable = new GLib.Cancellable();
var auto_config = new AutoConfig(this.auto_config_cancellable);
auto_config.get_config.begin(hostname, (obj, res) => {
try {
set_server_settings_from_autoconfig(auto_config, res);
} catch (Accounts.AutoConfigError err) {
debug("Error getting auto configuration: %s", err.message);
set_server_settings_from_hostname(hostname);
}
this.action_spinner.visible = false;
this.action_spinner.active = false;
});
} }
private void on_smtp_auth_changed() { private void on_smtp_auth_changed() {
@ -474,13 +566,29 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
} }
[GtkCallback] [GtkCallback]
private void on_create_button_clicked() { private void on_action_button_clicked() {
this.validate_account.begin(this.op_cancellable); if (this.stack.get_visible_child_name() == "user_settings") {
switch (this.provider) {
case Geary.ServiceProvider.GMAIL:
case Geary.ServiceProvider.OUTLOOK:
add_goa_account();
break;
case Geary.ServiceProvider.OTHER:
switch_to_server_settings();
break;
}
} else {
this.validate_account.begin(this.op_cancellable);
}
} }
[GtkCallback] [GtkCallback]
private void on_back_button_clicked() { private void on_back_button_clicked() {
this.editor.pop(); if (this.stack.get_visible_child_name() == "user_settings") {
this.editor.pop();
} else {
switch_to_user_settings();
}
} }
[GtkCallback] [GtkCallback]

View file

@ -31,7 +31,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
/** {@inheritDoc} */ /** {@inheritDoc} */
internal Gtk.Widget initial_widget { internal Gtk.Widget initial_widget {
get { get {
return this.show_welcome ? this.service_list : this.accounts_list; return this.accounts_list;
} }
} }
@ -73,10 +73,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
[GtkChild] private unowned Gtk.Frame accounts_list_frame; [GtkChild] private unowned Gtk.Frame accounts_list_frame;
[GtkChild] private unowned Gtk.Label add_service_label;
[GtkChild] private unowned Gtk.ListBox service_list;
private Gee.Map<Geary.AccountInformation,EditorEditPane> edit_pane_cache = private Gee.Map<Geary.AccountInformation,EditorEditPane> edit_pane_cache =
new Gee.HashMap<Geary.AccountInformation,EditorEditPane>(); new Gee.HashMap<Geary.AccountInformation,EditorEditPane>();
@ -97,12 +93,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
add_account(account, this.accounts.get_status(account)); add_account(account, this.accounts.get_status(account));
} }
this.service_list.set_header_func(Editor.seperator_headers);
this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.GMAIL));
this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.OUTLOOK));
this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.YAHOO));
this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.OTHER));
this.accounts.account_added.connect(on_account_added); this.accounts.account_added.connect(on_account_added);
this.accounts.account_status_changed.connect(on_account_status_changed); this.accounts.account_status_changed.connect(on_account_status_changed);
this.accounts.account_removed.connect(on_account_removed); this.accounts.account_removed.connect(on_account_removed);
@ -128,8 +118,8 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
base.destroy(); base.destroy();
} }
internal void show_new_account(Geary.ServiceProvider provider) { internal void show_new_account() {
this.editor.push(new EditorAddPane(this.editor, provider)); this.editor.push(new EditorAddPane(this.editor));
} }
internal void show_existing_account(Geary.AccountInformation account) { internal void show_existing_account(Geary.AccountInformation account) {
@ -171,13 +161,11 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
// pane and service list. // pane and service list.
this.welcome_panel.show(); this.welcome_panel.show();
this.accounts_list_frame.hide(); this.accounts_list_frame.hide();
this.add_service_label.hide();
} else { } else {
// There are some accounts available, so show them and // There are some accounts available, so show them and
// the full add service UI. // the full add service UI.
this.welcome_panel.hide(); this.welcome_panel.hide();
this.accounts_list_frame.show(); this.accounts_list_frame.show();
this.add_service_label.show();
} }
} }
@ -266,21 +254,9 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
} }
[GtkCallback] [GtkCallback]
private bool on_list_keynav_failed(Gtk.Widget widget, private void on_add_button_clicked() {
Gtk.DirectionType direction) { show_new_account();
bool ret = Gdk.EVENT_PROPAGATE;
if (direction == Gtk.DirectionType.DOWN &&
widget == this.accounts_list) {
this.service_list.child_focus(direction);
ret = Gdk.EVENT_STOP;
} else if (direction == Gtk.DirectionType.UP &&
widget == this.service_list) {
this.accounts_list.child_focus(direction);
ret = Gdk.EVENT_STOP;
}
return ret;
} }
} }
@ -424,75 +400,6 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
} }
private class Accounts.AddServiceProviderRow : EditorRow<EditorListPane> {
internal Geary.ServiceProvider provider;
private Gtk.Label service_name = new Gtk.Label("");
private Gtk.Image next_icon = new Gtk.Image.from_icon_name(
"go-next-symbolic", Gtk.IconSize.SMALL_TOOLBAR
);
public AddServiceProviderRow(Geary.ServiceProvider provider) {
this.provider = provider;
// Translators: Label for adding a generic email account
string? name = null;
switch (provider) {
case Geary.ServiceProvider.GMAIL:
name = _("Gmail");
break;
case Geary.ServiceProvider.OUTLOOK:
name = _("Outlook.com");
break;
case Geary.ServiceProvider.YAHOO:
name = _("Yahoo");
break;
case Geary.ServiceProvider.OTHER:
name = _("Other email providers");
break;
}
this.service_name.set_text(name);
this.service_name.set_hexpand(true);
this.service_name.halign = Gtk.Align.START;
this.service_name.show();
this.next_icon.show();
this.layout.add(this.service_name);
this.layout.add(this.next_icon);
}
public override void activated(EditorListPane pane) {
pane.accounts.add_goa_account.begin(
this.provider, pane.op_cancellable,
(obj, res) => {
bool add_local = false;
try {
pane.accounts.add_goa_account.end(res);
} catch (GLib.IOError.NOT_SUPPORTED err) {
// Not a supported type, so don't bother logging the error
add_local = true;
} catch (GLib.Error err) {
debug("Failed to add %s via GOA: %s",
this.provider.to_string(), err.message);
add_local = true;
}
if (add_local) {
pane.show_new_account(this.provider);
}
});
}
}
internal class Accounts.ReorderAccountCommand : Application.Command { internal class Accounts.ReorderAccountCommand : Application.Command {

View file

@ -34,6 +34,7 @@ client_vala_sources = files(
'application/goa-mediator.vala', 'application/goa-mediator.vala',
'application/secret-mediator.vala', 'application/secret-mediator.vala',
'accounts/accounts-autoconfig.vala',
'accounts/accounts-editor.vala', 'accounts/accounts-editor.vala',
'accounts/accounts-editor-add-pane.vala', 'accounts/accounts-editor-add-pane.vala',
'accounts/accounts-editor-edit-pane.vala', 'accounts/accounts-editor-edit-pane.vala',

View file

@ -7,34 +7,29 @@
<property name="title" translatable="yes">Add an account</property> <property name="title" translatable="yes">Add an account</property>
<property name="has_subtitle">False</property> <property name="has_subtitle">False</property>
<child> <child>
<object class="GtkGrid"> <object class="GtkButton" id="back_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_back_button_clicked" swapped="no"/>
<child> <child>
<object class="GtkButton" id="back_button"> <object class="GtkImage">
<property name="visible">True</property> <property name="visible">True</property>
<property name="receives_default">True</property> <property name="no_show_all">True</property>
<signal name="clicked" handler="on_back_button_clicked" swapped="no"/> <property name="icon_name">go-previous-symbolic</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="no_show_all">True</property>
<property name="icon_name">go-previous-symbolic</property>
</object>
</child>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="pack_type">start</property>
<property name="position">1</property>
</packing>
</child> </child>
<child> <child>
<object class="GtkGrid"> <object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
<property name="column_spacing">12</property> <property name="column_spacing">12</property>
<child> <child>
<object class="GtkSpinner" id="create_spinner"> <object class="GtkSpinner" id="action_spinner">
<property name="visible">True</property> <property name="visible">True</property>
</object> </object>
<packing> <packing>
@ -43,16 +38,13 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkButton" id="create_button"> <object class="GtkButton" id="action_button">
<property name="label" translatable="yes">_Create</property> <property name="label" translatable="yes">_Next</property>
<property name="visible">True</property> <property name="visible">True</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="use_underline">True</property> <property name="use_underline">True</property>
<signal name="clicked" handler="on_create_button_clicked" swapped="no"/> <signal name="clicked" handler="on_action_button_clicked" swapped="no"/>
<style>
<class name="suggested-action"/>
</style>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
@ -89,13 +81,14 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="margin">24</property> <property name="margin">24</property>
<child> <child>
<object class="GtkGrid" id="pane_content"> <object class="GtkStack" id="stack">
<property name="visible">True</property> <property name="visible">True</property>
<child> <child>
<object class="GtkFrame"> <object class="GtkFrame">
<property name="visible">True</property> <property name="visible">True</property>
<property name="label_xalign">0</property> <property name="label_xalign">0</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<property name="valign">start</property>
<child> <child>
<object class="GtkListBox" id="details_list"> <object class="GtkListBox" id="details_list">
<property name="visible">True</property> <property name="visible">True</property>
@ -105,102 +98,98 @@
</child> </child>
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="name">user_settings</property>
<property name="top_attach">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkGrid" id="receiving_panel"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<child> <child>
<object class="GtkLabel"> <object class="GtkGrid" id="receiving_panel">
<property name="visible">True</property> <property name="visible">True</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Receiving</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
<style>
<class name="geary-settings-heading"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child> <child>
<object class="GtkListBox" id="receiving_list"> <object class="GtkLabel">
<property name="visible">True</property> <property name="visible">True</property>
<property name="selection_mode">none</property> <property name="halign">start</property>
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/> <property name="label" translatable="yes">Receiving</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
<style>
<class name="geary-settings-heading"/>
</style>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkListBox" id="receiving_list">
<property name="visible">True</property>
<property name="selection_mode">none</property>
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkGrid" id="sending_panel">
<property name="visible">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Sending</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
<style>
<class name="geary-settings-heading"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkListBox" id="sending_list">
<property name="visible">True</property>
<property name="selection_mode">none</property>
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="name">server_settings</property>
<property name="top_attach">1</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkGrid" id="sending_panel">
<property name="visible">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Sending</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
<style>
<class name="geary-settings-heading"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkListBox" id="sending_list">
<property name="visible">True</property>
<property name="selection_mode">none</property>
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<style>
<class name="geary-accounts-editor-pane-content"/>
</style>
</object> </object>
</child> </child>
</object> </object>

View file

@ -7,6 +7,19 @@
<property name="title" translatable="yes">Accounts</property> <property name="title" translatable="yes">Accounts</property>
<property name="has_subtitle">False</property> <property name="has_subtitle">False</property>
<property name="show_close_button">True</property> <property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="add_button">
<property name="label" translatable="yes">_Add</property>
<property name="tooltip_text" translatable="yes">Add an account</property>
<property name="visible">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_add_button_clicked"/>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object> </object>
<object class="GtkAdjustment" id="pane_adjustment"> <object class="GtkAdjustment" id="pane_adjustment">
<property name="upper">100</property> <property name="upper">100</property>
@ -103,7 +116,6 @@
<object class="GtkListBox" id="accounts_list"> <object class="GtkListBox" id="accounts_list">
<property name="visible">True</property> <property name="visible">True</property>
<property name="selection_mode">none</property> <property name="selection_mode">none</property>
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
<signal name="row-activated" handler="on_row_activated" swapped="no"/> <signal name="row-activated" handler="on_row_activated" swapped="no"/>
</object> </object>
</child> </child>
@ -115,53 +127,9 @@
<property name="top_attach">1</property> <property name="top_attach">1</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkLabel" id="add_service_label">
<property name="visible">True</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Add an account</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
<style>
<class name="geary-settings-heading"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkListBox" id="service_list">
<property name="width_request">0</property>
<property name="visible">True</property>
<property name="valign">start</property>
<property name="selection_mode">none</property>
<signal name="keynav-failed" handler="on_list_keynav_failed" swapped="no"/>
<signal name="row-activated" handler="on_row_activated" swapped="no"/>
</object>
</child>
<child type="label_item">
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<style> <style>
<class name="geary-accounts-editor-pane-content"/> <class name="geary-accounts-editor-pane-content"/>
</style> </style>
</object> </object>
</child> </child>
</object> </object>