Commit 19ef1e63 authored by Julian Sparber's avatar Julian Sparber

Editor: use listbox layout to edit contact and secondary menu

GNOME uses now listboxes as the standart design pattern instead of a
grid. This replaces the grid and makes use of listboxes to allow the
user to edit a contact.
Some key features are:
 - Hide less important properties when not used
 - Dynamically fill the editor with properties so that the user has always
   one empty row to fill for each visible property
 - use a dialog for the birthday picker
 - Group properties by persona

ContactSheet:
Replace the edit button with a secondary menu.
The secondary menu contains share (hidden for now), edit, unlink and delete.
The reason for this change is that it doesn't make a lot of sense to have
delete and unlink inside the edit mode, since they don't require to commit changed.

Folks doesn't provied a staging features. So changes are commited
directly to the backend. The FakePersona and FakeIndividual are used
exactly for this. They work as a intermidiate layer so the editor can
change the persona directly and then when the user presses "done" the
changes can be copied to the real contact.
parent a937643a
......@@ -5,10 +5,9 @@
<file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-accounts-list.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-avatar-selector.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-editor.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-form.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-pane.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-crop-cheese-dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-editor-menu.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-in-app-notification.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-link-suggestion-grid.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/contacts-list-pane.ui</file>
......
......@@ -22,13 +22,15 @@
"--talk-name=org.gnome.OnlineAccounts",
/* Evolution Data server */
"--talk-name=org.gnome.evolution.dataserver.AddressBook9",
"--talk-name=org.gnome.evolution.dataserver.AddressBook10",
"--talk-name=org.gnome.evolution.dataserver.Sources5",
"--talk-name=org.gnome.evolution.dataserver.Subprocess.Backend.*",
/* Access for the default avatars */
"--filesystem=xdg-data/pixmaps/faces:ro:create",
/* Needed for dconf to work */
"--filesystem=xdg-run/dconf", "--filesystem=~/.config/dconf:ro",
"--talk-name=ca.desrt.dconf", "--env=DCONF_USER_CONFIG_DIR=.config/dconf"
"--talk-name=ca.desrt.dconf", "--env=DCONF_USER_CONFIG_DIR=.config/dconf",
"--socket=session-bus"
],
"cleanup": [
"/include",
......
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.22"/>
<menu id="edit-contact-menu">
<item>
<attribute name="action">edit.add.email-addresses.home</attribute>
<attribute name="label" translatable="yes">Home email</attribute>
</item>
<item>
<attribute name="action">edit.add.email-addresses.work</attribute>
<attribute name="label" translatable="yes">Work email</attribute>
</item>
<item>
<attribute name="action">edit.add.phone-numbers.cell</attribute>
<attribute name="label" translatable="yes">Mobile phone</attribute>
</item>
<item>
<attribute name="action">edit.add.phone-numbers.home</attribute>
<attribute name="label" translatable="yes">Home phone</attribute>
</item>
<item>
<attribute name="action">edit.add.phone-numbers.work</attribute>
<attribute name="label" translatable="yes">Work phone</attribute>
</item>
<item>
<attribute name="action">edit.add.urls</attribute>
<attribute name="label" translatable="yes">Website</attribute>
</item>
<item>
<attribute name="action">edit.add.nickname</attribute>
<attribute name="label" translatable="yes">Nickname</attribute>
</item>
<item>
<attribute name="action">edit.add.birthday</attribute>
<attribute name="label" translatable="yes">Birthday</attribute>
</item>
<item>
<attribute name="action">edit.add.postal-addresses.home</attribute>
<attribute name="label" translatable="yes">Home address</attribute>
</item>
<item>
<attribute name="action">edit.add.postal-addresses.work</attribute>
<attribute name="label" translatable="yes">Work address</attribute>
</item>
<item>
<attribute name="action">edit.add.notes</attribute>
<attribute name="label" translatable="yes">Notes</attribute>
</item>
</menu>
<template class="ContactsContactEditor" parent="ContactsContactForm">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkActionBar">
<property name="visible">True</property>
<child>
<object class="GtkMenuButton" id="add_detail_button">
<property name="visible">True</property>
<property name="menu_model">edit-contact-menu</property>
<property name="use_popover">True</property>
<property name="direction">up</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="spacing">6</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">New Detail</property>
</object>
</child>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pixel_size">16</property>
<property name="icon_name">pan-down-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="remove_button">
<property name="visible">True</property>
<style>
<class name="destructive-action"/>
</style>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
</child>
</template>
</interface>
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.22 -->
<template class="ContactsContactForm" parent="GtkGrid">
<property name="visible">True</property>
<child>
<object class="GtkScrolledWindow" id="main_sw">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">none</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="ContactsMaxWidthBin">
<property name="visible">True</property>
<property name="max_width">600</property>
<property name="halign">center</property>
<child>
<object class="GtkGrid" id="container_grid">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="row_spacing">12</property>
<property name="column_spacing">12</property>
<property name="margin">12</property>
<property name="margin_top">36</property>
<property name="margin_bottom">36</property>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="ContactsContactPane" parent="GtkStack">
<template class="ContactsContactPane" parent="GtkScrolledWindow">
<property name="visible">True</property>
<property name="visible-child">none_selected_page</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">none</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkGrid" id="none_selected_page">
<object class="HdyColumn">
<property name="visible">True</property>
<property name="width_request">300</property>
<property name="orientation">vertical</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="row_spacing">6</property>
<property name="maximum-width">600</property>
<property name="linear-growth-width">400</property>
<property name="margin-top">32</property>
<property name="margin-bottom">32</property>
<property name="margin-left">24</property>
<property name="margin-right">24</property>
<child>
<object class="GtkImage">
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="icon_name">avatar-default-symbolic</property>
<property name="vexpand">True</property>
<property name="valign">end</property>
<property name="pixel_size">144</property>
<style>
<class name="contacts-watermark"/>
</style>
<property name="visible-child">none_selected_page</property>
<child>
<object class="GtkGrid" id="none_selected_page">
<property name="visible">True</property>
<property name="width_request">300</property>
<property name="orientation">vertical</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="icon_name">avatar-default-symbolic</property>
<property name="vexpand">True</property>
<property name="valign">end</property>
<property name="pixel_size">144</property>
<style>
<class name="contacts-watermark"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">Select a contact</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="valign">start</property>
<property name="margin_bottom">70</property>
<style>
<class name="contacts-watermark"/>
</style>
</object>
</child>
</object>
<packing>
<property name="name">none-selected-page</property>
</packing>
</child>
<child>
<object class="GtkBox" id="contact_sheet_page">
<property name="visible">True</property>
</object>
<packing>
<property name="name">contact-sheet-page</property>
</packing>
</child>
<child>
<object class="GtkBox" id="contact_editor_page">
<property name="visible">True</property>
</object>
<packing>
<property name="name">contact-editor-page</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">Select a contact</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="valign">start</property>
<property name="margin_bottom">70</property>
<style>
<class name="contacts-watermark"/>
</style>
</object>
</child>
</object>
<packing>
<property name="name">none-selected-page</property>
</packing>
</child>
<child>
<object class="GtkBox" id="contact_sheet_page">
<property name="visible">True</property>
</object>
<packing>
<property name="name">contact-sheet-page</property>
</packing>
</child>
<child>
<object class="GtkBox" id="contact_editor_page">
<property name="visible">True</property>
</object>
<packing>
<property name="name">contact-editor-page</property>
</packing>
</child>
</template>
</interface>
<?xml version="1.0" encoding="utf-8"?>
<interface>
<object class="GtkPopoverMenu" id="editor_menu">
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="margin">10</property>
<child>
<object class="GtkModelButton">
<property name="visible">True</property>
<property name="action-name">persona.change-addressbook</property>
<property name="text" translatable="yes">Change Addressbook</property>
</object>
</child>
</object>
</child>
</object>
</interface>
......@@ -94,6 +94,48 @@
</object>
</child>
</object>
<object class="GtkPopoverMenu" id="contact_sheet_menu">
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="margin">10</property>
<child>
<object class="GtkModelButton">
<property name="visible">False</property>
<property name="action-name">window.share-contact</property>
<property name="text" translatable="yes">Share</property>
</object>
</child>
<child>
<object class="GtkModelButton">
<property name="visible">True</property>
<property name="action-name">window.edit-contact</property>
<property name="text" translatable="yes">Edit</property>
</object>
</child>
<child>
<object class="GtkModelButton" id="unlink_button">
<property name="visible">True</property>
<property name="action-name">window.unlink-contact</property>
<property name="text" translatable="yes">Unlink</property>
</object>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
</object>
</child>
<child>
<object class="GtkModelButton">
<property name="visible">True</property>
<property name="action-name">window.delete-contact</property>
<property name="text" translatable="yes">Delete</property>
</object>
</child>
</object>
</child>
</object>
<template class="ContactsWindow" parent="GtkApplicationWindow">
<property name="can_focus">False</property>
<property name="default_width">800</property>
......@@ -244,44 +286,43 @@
</packing>
</child>
<child>
<object class="GtkButton" id="edit_button">
<object class="GtkToggleButton" id="favorite_button">
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="valign">center</property>
<property name="tooltip_text" translatable="yes">Edit details</property>
<signal name="clicked" handler="on_edit_button_clicked"/>
<signal name="toggled" handler="on_favorite_button_toggled"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-edit-symbolic</property>
<property name="icon_name">starred-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="favorite_button">
<object class="GtkMenuButton" id="contact_menu_button">
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="valign">center</property>
<signal name="toggled" handler="on_favorite_button_toggled"/>
<property name="popover">contact_sheet_menu</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">starred-symbolic</property>
<property name="icon_size">1</property>
<property name="icon_name">view-more-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
......
......@@ -2,24 +2,11 @@
* GNOME Contacts
*/
.contacts-map {
background-color: @theme_bg_color;
}
/* The contacts in the left pane */
.contacts-contact-list {
background-color: transparent;
}
/* A single row in the contact list pane */
row.contact-data-row {
}
/* Styles for a ContactsContactForm */
.contacts-contact-form {
background-color: mix(@theme_bg_color, @theme_base_color, 0.4);
}
.contacts-suggestion {
border-top: 1px solid @borders;
background-color: shade(@theme_bg_color, 0.9);
......@@ -69,3 +56,15 @@ row.contact-data-row {
text-shadow: none; -gtk-icon-shadow: none;
border: 1px solid rgba(205, 199, 194, 0.5);
}
/* remove padding from ListBoxRow so that the revealer doesn't jump */
row.editor-property-row {
padding: 0px;
}
popover list {
background-color: @theme_bg_color;
}
popover list row:hover {
background-color: @theme_selected_fg_color
}
/*
* Copyright (C) 2019 Purism SPC
*
* Author: Julian Sparber
*
* 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 Hdy;
using Gtk;
using Folks;
public class Contacts.AddressbookList : ListBox {
private BackendStore store;
private Widget? checkmark;
private AddressbookRow? marked_row;
private bool show_icon;
public signal void addressbook_selected ();
public AddressbookList (BackendStore store, bool icon = true) {
this.store = store;
this.show_icon = icon;
this.set_header_func (list_box_update_header_func);
this.update ();
}
void list_box_update_header_func (ListBoxRow row, ListBoxRow? before) {
if (before == null) {
row.set_header (null);
} else if (row.get_header () == null) {
var header = new Separator (Orientation.HORIZONTAL);
header.show ();
row.set_header (header);
}
}
public override void row_activated (ListBoxRow row) {
var addressbook = row as AddressbookRow;
if (addressbook == null)
return;
if (marked_row != null &&
marked_row == addressbook) {
return;
}
if (marked_row != null) {
marked_row.unselect ();
}
addressbook.select ();
marked_row = addressbook;
addressbook_selected ();
}
public void update () {
foreach (var child in get_children ()) {
child.destroy ();
}
// Fill the list with address book
PersonaStore[] eds_stores = Utils.get_eds_address_books_from_backend (this.store);
debug ("Found %d EDS stores", eds_stores.length);
PersonaStore? local_store = null;
foreach (var persona_store in eds_stores) {
if (persona_store.id == "system-address-book") {
local_store = persona_store;
continue;
}
var source = (persona_store as Edsf.PersonaStore).source;
var parent_source = eds_source_registry.ref_source (source.parent);
var provider_name = ContactUtils.format_persona_store_name (persona_store);
debug ("Contact store \"%s\"", provider_name);
var source_account_id = "";
if (parent_source.has_extension (E.SOURCE_EXTENSION_GOA)) {
var goa_source_ext = parent_source.get_extension (E.SOURCE_EXTENSION_GOA) as E.SourceGoa;
source_account_id = goa_source_ext.account_id;
}
Gtk.Image provider_image = null;
if (this.show_icon) {
if (source_account_id != "")
provider_image = Contacts.get_icon_for_goa_account (source_account_id);
else
provider_image = new Image.from_icon_name (Config.APP_ID, IconSize.DIALOG);
}
var row = new AddressbookRow (provider_name, parent_source.display_name, provider_image);
add (row);
}
if (local_store != null) {
var provider_image = (this.show_icon) ? new Image.from_icon_name (Config.APP_ID, IconSize.DIALOG) : null;
var local_row = new AddressbookRow (_("Local Address Book"), null, provider_image);
add (local_row);
}
/*
if (select_active &&
local_store == this.contacts_store.aggregator.primary_store) {
row_activated (local_row);
}
*/
show_all ();
}
}
public class Contacts.AddressbookRow : Hdy.ActionRow {
Widget checkmark;
public AddressbookRow (string title, string? subtitle, Widget? image = null) {
this.set_selectable (false);
if (image != null) {
this.add_prefix (image);
}
this.title = title;
if (subtitle != null) {
this.subtitle = subtitle;
}