summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHendrik Müller <henne90gen@gmail.com>2022-11-18 20:13:04 +0100
committerNiels De Graef <nielsdegraef@gmail.com>2023-02-11 06:34:02 +0000
commit19f37fbe3e5be87fefe79d1fe6f3c66319acb69e (patch)
treee5bce185478678ea4758c560b26c227043fc524e /src
parentac886150ac4533035e8451a2035f05ce412ef2cf (diff)
downloadgnome-contacts-19f37fbe3e5be87fefe79d1fe6f3c66319acb69e.tar.gz
contact: Add QR code to share individual contacts
Sharing contacts in an easy and offline way is currently not possible. Most mobile phones have a camera and are capable of scanning QR codes. The vCard format is widely used to easily exchange contact information. A contact can be saved in vCard format into a QR code. A button with a QR code icon is added next to the "favourite" and "edit" buttons. When the user presses this button, a dialog opens up, which shows a QR code containing the current contacts data in vCard format.
Diffstat (limited to 'src')
-rw-r--r--src/contacts-main-window.vala34
-rw-r--r--src/contacts-qr-code-dialog.vala109
-rw-r--r--src/meson.build2
3 files changed, 135 insertions, 10 deletions
diff --git a/src/contacts-main-window.vala b/src/contacts-main-window.vala
index 0d62184..ca1181f 100644
--- a/src/contacts-main-window.vala
+++ b/src/contacts-main-window.vala
@@ -25,10 +25,11 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
{ "new-contact", new_contact },
{ "edit-contact", edit_contact },
{ "stop-editing-contact", stop_editing_contact, "b" },
+ { "toggle-favorite", toggle_favorite },
{ "link-marked-contacts", link_marked_contacts },
{ "delete-marked-contacts", delete_marked_contacts },
{ "export-marked-contacts", export_marked_contacts },
- // { "share-contact", share_contact },
+ { "show-contact-qr-code", show_contact_qr_code },
{ "unlink-contact", unlink_contact },
{ "delete-contact", delete_contact },
{ "sort-on", null, "s", "'surname'", sort_on_changed },
@@ -68,9 +69,11 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
[GtkChild]
private unowned Gtk.MenuButton primary_menu_button;
[GtkChild]
- private unowned Gtk.Box contact_sheet_buttons;
+ private unowned Gtk.MenuButton contact_hamburger_menu_button;
+ private unowned Gtk.PopoverMenu contact_hamburger_popover_menu;
+ private Gtk.Button favorite_button;
[GtkChild]
- private unowned Gtk.ToggleButton favorite_button;
+ private unowned Gtk.Box contact_sheet_buttons;
private bool ignore_favorite_button_toggled;
[GtkChild]
private unowned Gtk.Button add_button;
@@ -135,6 +138,12 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
unowned var sort_key = this.settings.sort_on_surname? "surname" : "firstname";
var sort_action = (SimpleAction) this.lookup_action ("sort-on");
sort_action.set_state (new Variant.string (sort_key));
+
+ contact_hamburger_popover_menu = (Gtk.PopoverMenu) contact_hamburger_menu_button.get_popover ();
+ favorite_button = new Gtk.Button.with_label (_("Mark as Favorite"));
+ favorite_button.set_action_name ("win.toggle-favorite");
+ favorite_button.set_css_classes ({"flat", "favorite-button"});
+ contact_hamburger_popover_menu.add_child (favorite_button, "favorite-toggle");
}
private void restore_window_state () {
@@ -253,8 +262,13 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
this.contact_pane.edit_contact ();
}
- [GtkCallback]
- private void on_favorite_button_toggled (Gtk.ToggleButton button) {
+ private void show_contact_qr_code (GLib.SimpleAction action, GLib.Variant? parameter) {
+ unowned var selected = this.store.get_selected_contact ();
+ var dialog = new QrCodeDialog.for_contact (selected, get_root () as Gtk.Window);
+ dialog.show ();
+ }
+
+ private void toggle_favorite (GLib.SimpleAction action, GLib.Variant? parameter) {
// Don't change the contact being favorite while switching between the two of them
if (this.ignore_favorite_button_toggled)
return;
@@ -263,6 +277,9 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
return_if_fail (selected != null);
selected.is_favourite = !selected.is_favourite;
+
+ this.state = UiState.NORMAL;
+ this.contact_hamburger_popover_menu.popdown ();
}
[GtkCallback]
@@ -445,13 +462,10 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
// clearing right_header
this.right_header.title_widget = new Adw.WindowTitle ("", "");
if (selected != null) {
- this.ignore_favorite_button_toggled = true;
- this.favorite_button.active = selected.is_favourite;
- this.ignore_favorite_button_toggled = false;
if (selected.is_favourite)
- this.favorite_button.tooltip_text = _("Unmark as favorite");
+ this.favorite_button.set_label (_("Unmark as Favorite"));
else
- this.favorite_button.tooltip_text = _("Mark as favorite");
+ this.favorite_button.set_label (_("Mark as Favorite"));
}
this.state = UiState.SHOWING;
}
diff --git a/src/contacts-qr-code-dialog.vala b/src/contacts-qr-code-dialog.vala
new file mode 100644
index 0000000..02c0539
--- /dev/null
+++ b/src/contacts-qr-code-dialog.vala
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 Elias Entrup <elias-git@flump.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+using GLib;
+
+[GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-qr-code-dialog.ui")]
+public class Contacts.QrCodeDialog : Adw.PreferencesWindow {
+
+ [GtkChild]
+ private unowned Gtk.Picture qr_image;
+
+ [GtkChild]
+ private unowned Gtk.Label qr_subtitle;
+
+ public QrCodeDialog.for_contact (Individual individual, Gtk.Window? parent = null) {
+ Object (transient_for: parent);
+
+ var subtitle = GLib.Markup.printf_escaped (_("Scan the QR code to save the contact <b>%s</b>."),
+ individual.display_name);
+ this.qr_subtitle.set_markup (subtitle);
+
+ var individuals = new Gee.ArrayList<Individual> ();
+ individuals.add (individual);
+
+ var stringstream = new GLib.MemoryOutputStream.resizable ();
+ var op = new Io.VCardExportOperation (individuals, stringstream);
+ op.execute.begin ((obj, res) => {
+ try {
+ op.execute.end (res);
+ uint8[] chars = {0};
+ stringstream.write (chars);
+ stringstream.close ();
+ } catch (Error e) {
+ warning ("ERROR: %s", e.message);
+ }
+
+ var content = (string) stringstream.steal_data ();
+ int QR_IMAGE_SIZE = 300;
+ var scale = this.qr_image.get_scale_factor ();
+ create_qr_code (content, QR_IMAGE_SIZE * scale);
+ });
+ }
+
+ private void create_qr_code (string content, int size) {
+ if (content == "") {
+ warning ("Failed to create QR code: no content");
+ return;
+ }
+
+ var result = new QRencode.QRcode.encodeString (content,
+ 0,
+ QRencode.EcLevel.M,
+ QRencode.Mode.B8,
+ 1);
+ if (result == null) {
+ warning ("Failed to create QR code: libqrencode error");
+ return;
+ }
+
+ var qr_size = result.width;
+ var pixel_size = (int) double.max (1, size / qr_size);
+ var total_size = qr_size * pixel_size;
+ var BYTES_PER_R8G8B8 = 3;
+ var qr_matrix = new GLib.ByteArray.sized ((uint)(total_size * total_size * pixel_size * BYTES_PER_R8G8B8));
+
+ for (var column = 0; column < total_size; column++) {
+ for (var i = 0; i < pixel_size; i++) {
+ for (var row = 0; row < total_size / pixel_size; row++) {
+ if ((result.data[qr_size*row + column] & 0x01) > 0) {
+ fill_pixel (qr_matrix, 0x00, pixel_size);
+ } else {
+ fill_pixel (qr_matrix, 0xff, pixel_size);
+ }
+ }
+ }
+ }
+
+ var bytes = ByteArray.free_to_bytes (qr_matrix);
+ var paintable = new Gdk.MemoryTexture (total_size, total_size,
+ Gdk.MemoryFormat.R8G8B8,
+ bytes,
+ total_size * BYTES_PER_R8G8B8);
+ this.qr_image.set_paintable (paintable);
+ }
+
+ private void fill_pixel (GLib.ByteArray array, uint8 val, int pixel_size) {
+ for (uint i = 0; i < pixel_size; i++) {
+ array.append ({val}); // R
+ array.append ({val}); // G
+ array.append ({val}); // B
+ }
+ }
+}
+
diff --git a/src/meson.build b/src/meson.build
index 87a9fd0..6f680e8 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -59,6 +59,7 @@ contacts_deps = [
# libedataserverui,
libportal_dep,
math,
+ libqrencode_dep,
]
if get_option('goa')
@@ -94,6 +95,7 @@ contacts_vala_sources = files(
'contacts-link-suggestion-grid.vala',
'contacts-linked-personas-dialog.vala',
'contacts-main-window.vala',
+ 'contacts-qr-code-dialog.vala',
'contacts-preferences-window.vala',
'contacts-settings.vala',
'contacts-setup-window.vala',