diff options
author | Hendrik Müller <henne90gen@gmail.com> | 2022-11-18 20:13:04 +0100 |
---|---|---|
committer | Niels De Graef <nielsdegraef@gmail.com> | 2023-02-11 06:34:02 +0000 |
commit | 19f37fbe3e5be87fefe79d1fe6f3c66319acb69e (patch) | |
tree | e5bce185478678ea4758c560b26c227043fc524e /src | |
parent | ac886150ac4533035e8451a2035f05ce412ef2cf (diff) | |
download | gnome-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.vala | 34 | ||||
-rw-r--r-- | src/contacts-qr-code-dialog.vala | 109 | ||||
-rw-r--r-- | src/meson.build | 2 |
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', |