diff options
25 files changed, 483 insertions, 568 deletions
diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index dd25c5f5..ea23b82a 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -64,8 +64,10 @@ public abstract class EncryptionPreferencesEntry : Object { public interface ContactDetailsProvider : Object { public abstract string id { get; } + public abstract string tab { get; } public abstract void populate(Conversation conversation, ContactDetails contact_details, WidgetType type); + public abstract Object? get_widget(Conversation conversation); } public class ContactDetails : Object { diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 70665903..5cc1a8bf 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -89,6 +89,7 @@ set(RESOURCE_LIST menu_conversation.ui menu_encryption.ui message_item_widget_edit_mode.ui + muc_member_list_row.ui occupant_list.ui occupant_list_item.ui quote.ui diff --git a/main/data/conversation_details.css b/main/data/conversation_details.css index 0eaf60c0..f0cbf666 100644 --- a/main/data/conversation_details.css +++ b/main/data/conversation_details.css @@ -1,7 +1,31 @@ -.extended-headerbar { - background-color: @headerbar_bg_color; -} .extended-headerbar-end { padding-bottom: 24px; border-bottom: 1px solid @borders; +} + +.extended-headerbar { + background-color: @headerbar_bg_color; + padding-bottom: 0; +} + +.dino-underlined-tabs .toggle { + background: none; + padding: 0; + margin: 0 12px 0 0; + border-left: none; + min-width: 0; +} + +.dino-underlined-tabs .toggle label { + border-bottom: 2px solid transparent; + margin: 0; + padding: 6px; +} + +.dino-underlined-tabs .toggle:checked label { + border-bottom-color: @accent_color; +} + +.dino-underlined-tabs .toggle:hover:not(:checked) label { + border-bottom-color: alpha(@accent_color, 0.25); }
\ No newline at end of file diff --git a/main/data/conversation_details.ui b/main/data/conversation_details.ui index 5ee156bb..593dd4bf 100644 --- a/main/data/conversation_details.ui +++ b/main/data/conversation_details.ui @@ -134,25 +134,46 @@ </child> </object> </child> + <child> + <object class="GtkStackSwitcher"> + <property name="stack">stack</property> + <property name="halign">start</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <style> + <class name="dino-underlined-tabs"/> + </style> + </object> + </child> </object> </child> </object> </child> <child> - <object class="GtkScrolledWindow"> - <property name="propagate-natural-height">True</property> + <object class="GtkStack" id="stack"> <child> - <object class="AdwClamp"> - <child> - <object class="GtkBox" id="about_box"> - <property name="orientation">vertical</property> - <property name="spacing">12</property> - <property name="margin-end">12</property> - <property name="margin-start">12</property> - <property name="margin-top">12</property> - <property name="margin-bottom">40</property> + <object class="GtkStackPage"> + <property name="title">About</property> + <property name="name">about</property> + <property name="child"> + <object class="GtkScrolledWindow"> + <property name="propagate-natural-height">True</property> + <child> + <object class="AdwClamp"> + <child> + <object class="GtkBox" id="about_box"> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <property name="margin-end">12</property> + <property name="margin-start">12</property> + <property name="margin-top">18</property> + <property name="margin-bottom">40</property> + </object> + </child> + </object> + </child> </object> - </child> + </property> </object> </child> </object> diff --git a/main/data/gresource.xml b/main/data/gresource.xml index 661185ab..88858e13 100644 --- a/main/data/gresource.xml +++ b/main/data/gresource.xml @@ -70,6 +70,7 @@ <file>menu_conversation.ui</file> <file>menu_encryption.ui</file> <file>message_item_widget_edit_mode.ui</file> + <file>muc_member_list_row.ui</file> <file>occupant_list.ui</file> <file>occupant_list_item.ui</file> <file>quote.ui</file> diff --git a/main/data/muc_member_list_row.ui b/main/data/muc_member_list_row.ui new file mode 100644 index 00000000..cefad3fc --- /dev/null +++ b/main/data/muc_member_list_row.ui @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="GtkListItem"> + <property name="child"> + <object class="GtkBox"> + <property name="orientation">horizontal</property> + <property name="margin-top">10</property> + <property name="margin-bottom">10</property> + <property name="margin-start">7</property> + <property name="margin-end">14</property> + <child> + <object class="DinoUiAvatarPicture" id="picture"> + <property name="height-request">35</property> + <property name="width-request">35</property> + <property name="valign">start</property> + <binding name="model"> + <lookup name="avatar" type="DinoUiViewModelConferenceMemberListRow"> + <lookup name="item">GtkListItem</lookup> + </lookup> + </binding> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="margin-start">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="name_label"> + <property name="max_width_chars">1</property> + <property name="ellipsize">end</property> + <property name="hexpand">True</property> + <property name="margin-end">7</property> + <property name="xalign">0</property> + <binding name="label"> + <lookup name="name" type="DinoUiViewModelConferenceMemberListRow"> + <lookup name="item">GtkListItem</lookup> + </lookup> + </binding> + </object> + </child> + <child> + <object class="GtkLabel" id="affiliation_label"> + <property name="margin-end">6</property> + <property name="xalign">1</property> + <binding name="label"> + <lookup name="affiliation-str" type="DinoUiViewModelConferenceMemberListRow"> + <lookup name="item">GtkListItem</lookup> + </lookup> + </binding> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="spacing">4</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkLabel" id="jid_label"> + <property name="ellipsize">end</property> + <property name="xalign">0</property> + <property name="margin-end">7</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="0.8"/> + </attributes> + <binding name="label"> + <lookup name="jid" type="DinoUiViewModelConferenceMemberListRow"> + <lookup name="item">GtkListItem</lookup> + </lookup> + </binding> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </property> + </template> +</interface>
\ No newline at end of file diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala index 3c816a77..796146f8 100644 --- a/main/src/ui/application.vala +++ b/main/src/ui/application.vala @@ -145,6 +145,20 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application { }); add_action(open_conversation_action); + SimpleAction open_conversation_details_action = new SimpleAction("open-conversation-details", new VariantType.tuple(new VariantType[]{VariantType.INT32, VariantType.STRING})); + open_conversation_details_action.activate.connect((variant) => { + int conversation_id = variant.get_child_value(0).get_int32(); + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(conversation_id); + if (conversation == null) return; + + string stack_value = variant.get_child_value(1).get_string(); + + var conversation_details = ConversationDetails.setup_dialog(conversation, stream_interactor, window); + conversation_details.stack.visible_child_name = stack_value; + conversation_details.present(); + }); + add_action(open_conversation_details_action); + SimpleAction deny_subscription_action = new SimpleAction("deny-subscription", VariantType.INT32); deny_subscription_action.activate.connect((variant) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32()); diff --git a/main/src/ui/contact_details/permissions_provider.vala b/main/src/ui/contact_details/permissions_provider.vala index c7ea4a1c..a0ca4d13 100644 --- a/main/src/ui/contact_details/permissions_provider.vala +++ b/main/src/ui/contact_details/permissions_provider.vala @@ -6,6 +6,7 @@ namespace Dino.Ui.ContactDetails { public class PermissionsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "permissions"; } } + public string tab { get { return "about"; } } private StreamInteractor stream_interactor; @@ -25,6 +26,10 @@ public class PermissionsProvider : Plugins.ContactDetailsProvider, Object { contact_details.add("Permissions", _("Request permission to send messages"), "", voice_request); } } + + public Object? get_widget(Conversation conversation) { + return null; + } } } diff --git a/main/src/ui/contact_details/settings_provider.vala b/main/src/ui/contact_details/settings_provider.vala index 6a680f64..c6b87652 100644 --- a/main/src/ui/contact_details/settings_provider.vala +++ b/main/src/ui/contact_details/settings_provider.vala @@ -6,6 +6,7 @@ namespace Dino.Ui.ContactDetails { public class SettingsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "chat_settings"; } } + public string tab { get { return "about"; } } private StreamInteractor stream_interactor; @@ -69,6 +70,10 @@ public class SettingsProvider : Plugins.ContactDetailsProvider, Object { combobox.append("off", _("Off")); return combobox; } + + public Object? get_widget(Conversation conversation) { + return null; + } } } diff --git a/main/src/ui/conversation_details.vala b/main/src/ui/conversation_details.vala index 4c6a0481..4b8a53b3 100644 --- a/main/src/ui/conversation_details.vala +++ b/main/src/ui/conversation_details.vala @@ -1,3 +1,4 @@ +using Dino; using Dino.Entities; using Xmpp; using Xmpp.Xep; @@ -24,6 +25,17 @@ namespace Dino.Ui.ConversationDetails { public void bind_dialog(Model.ConversationDetails model, ViewModel.ConversationDetails view_model, StreamInteractor stream_interactor) { view_model.avatar = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(model.conversation); view_model.show_blocked = model.conversation.type_ == Conversation.Type.CHAT && stream_interactor.get_module(BlockingManager.IDENTITY).is_supported(model.conversation.account); + view_model.members_sorted.set_model(model.members); + view_model.members.set_map_func((item) => { + var conference_member = (Ui.Model.ConferenceMember) item; + Jid? nick_jid = stream_interactor.get_module(MucManager.IDENTITY).get_occupant_jid(model.conversation.account, model.conversation.counterpart, conference_member.jid); + return new Ui.ViewModel.ConferenceMemberListRow() { + avatar = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(model.conversation, conference_member.jid), + name = nick_jid != null ? nick_jid.resourcepart : conference_member.jid.localpart, + jid = conference_member.jid.to_string(), + affiliation = conference_member.affiliation + }; + }); if (model.domain_blocked) { view_model.blocked = DOMAIN; @@ -123,10 +135,11 @@ namespace Dino.Ui.ConversationDetails { }); } - public Window setup_dialog(Conversation conversation, StreamInteractor stream_interactor, Window parent) { + public Dialog setup_dialog(Conversation conversation, StreamInteractor stream_interactor, Window parent) { var dialog = new Dialog() { transient_for = parent }; var model = new Model.ConversationDetails(); - populate_dialog(model, conversation, stream_interactor); + model.populate(stream_interactor, conversation); +// populate_dialog(model, conversation, stream_interactor); bind_dialog(model, dialog.model, stream_interactor); dialog.model.about_rows.append(new ViewModel.PreferencesRow.Text() { @@ -171,6 +184,10 @@ namespace Dino.Ui.ConversationDetails { app.plugin_registry.register_contact_details_entry(new ContactDetails.PermissionsProvider(stream_interactor)); foreach (Plugins.ContactDetailsProvider provider in app.plugin_registry.contact_details_entries) { + var preferences_group = (Adw.PreferencesGroup) provider.get_widget(conversation); + if (preferences_group != null) { + dialog.add_encryption_tab_element((Adw.PreferencesGroup) provider.get_widget(conversation)); + } provider.populate(conversation, contact_details, Plugins.WidgetType.GTK4); } @@ -195,14 +212,13 @@ namespace Dino.Ui.ConversationDetails { }; switch (category) { - case "Encryption": - dialog.model.encryption_rows.append(view_model); - break; case "Permissions": case "Local Settings": case "Settings": dialog.model.settings_rows.append(view_model); break; + default: + break; } } } diff --git a/main/src/ui/conversation_titlebar/menu_entry.vala b/main/src/ui/conversation_titlebar/menu_entry.vala index 8a3d525f..7a6688ae 100644 --- a/main/src/ui/conversation_titlebar/menu_entry.vala +++ b/main/src/ui/conversation_titlebar/menu_entry.vala @@ -25,7 +25,8 @@ class MenuEntry : Plugins.ConversationTitlebarEntry, Object { SimpleActionGroup action_group = new SimpleActionGroup(); SimpleAction details_action = new SimpleAction("details", null); details_action.activate.connect((parameter) => { - open_conversation_details(); + var variant = new Variant.tuple(new Variant[] {new Variant.int32(conversation.id), new Variant.string("about")}); + GLib.Application.get_default().activate_action("open-conversation-details", variant); }); action_group.insert(details_action); SimpleAction close_action = new SimpleAction("close", null); diff --git a/main/src/ui/util/preference_group.vala b/main/src/ui/util/preference_group.vala index 8f3a34f3..fca3d677 100644 --- a/main/src/ui/util/preference_group.vala +++ b/main/src/ui/util/preference_group.vala @@ -11,96 +11,98 @@ namespace Dino.Ui.Util { for (int preference_group_i = 0; preference_group_i < row_view_models.get_n_items(); preference_group_i++) { var preferences_row = (ViewModel.PreferencesRow.Any) row_view_models.get_item(preference_group_i); - Widget? w = null; + Widget? w = row_to_preference_row(preferences_row); + if (w == null) continue; - var entry_view_model = preferences_row as ViewModel.PreferencesRow.Entry; - if (entry_view_model != null) { - Adw.EntryRow view = new Adw.EntryRow() { title = entry_view_model.title, show_apply_button=true }; - entry_view_model.bind_property("text", view, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL, (_, from, ref to) => { - var str = (string) from; - to = str ?? ""; - return true; - }); - view.apply.connect(() => { - entry_view_model.changed(); - }); - w = view; - } + preference_group.add(w); + } - var password_view_model = preferences_row as ViewModel.PreferencesRow.PrivateText; - if (password_view_model != null) { - Adw.PasswordEntryRow view = new Adw.PasswordEntryRow() { title = password_view_model.title, show_apply_button=true }; - password_view_model.bind_property("text", view, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL, (_, from, ref to) => { - var str = (string) from; - to = str ?? ""; - return true; - }); - view.apply.connect(() => { - password_view_model.changed(); - }); + return preference_group; + } - w = view; - } + public Adw.PreferencesRow? row_to_preference_row(ViewModel.PreferencesRow.Any preferences_row) { + var entry_view_model = preferences_row as ViewModel.PreferencesRow.Entry; + if (entry_view_model != null) { + Adw.EntryRow view = new Adw.EntryRow() { title = entry_view_model.title, show_apply_button=true }; + entry_view_model.bind_property("text", view, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL, (_, from, ref to) => { + var str = (string) from; + to = str ?? ""; + return true; + }); + view.apply.connect(() => { + entry_view_model.changed(); + }); + return view; + } + + var password_view_model = preferences_row as ViewModel.PreferencesRow.PrivateText; + if (password_view_model != null) { + Adw.PasswordEntryRow view = new Adw.PasswordEntryRow() { title = password_view_model.title, show_apply_button=true }; + password_view_model.bind_property("text", view, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL, (_, from, ref to) => { + var str = (string) from; + to = str ?? ""; + return true; + }); + view.apply.connect(() => { + password_view_model.changed(); + }); + + return view; + } - var row_text = preferences_row as ViewModel.PreferencesRow.Text; - if (row_text != null) { - w = new Adw.ActionRow() { - title = row_text.title, - subtitle = row_text.text, + var row_text = preferences_row as ViewModel.PreferencesRow.Text; + if (row_text != null) { + var view = new Adw.ActionRow() { + title = row_text.title, + subtitle = row_text.text, #if Adw_1_3 - subtitle_selectable = true, + subtitle_selectable = true, #endif - }; - w.add_css_class("property"); + }; + view.add_css_class("property"); - Util.force_css(w, "row.property > box.header > box.title > .title { font-weight: 400; font-size: 9pt; opacity: 0.55; }"); - Util.force_css(w, "row.property > box.header > box.title > .subtitle { font-size: inherit; opacity: 1; }"); - } + Util.force_css(view, "row.property > box.header > box.title > .title { font-weight: 400; font-size: 9pt; opacity: 0.55; }"); + Util.force_css(view, "row.property > box.header > box.title > .subtitle { font-size: inherit; opacity: 1; }"); + return view; + } - var toggle_view_model = preferences_row as ViewModel.PreferencesRow.Toggle; - if (toggle_view_model != null) { - var view = new Adw.ActionRow() { title = toggle_view_model.title, subtitle = toggle_view_model.subtitle }; - var toggle = new Switch() { valign = Align.CENTER }; - view.activatable_widget = toggle; - view.add_suffix(toggle); - toggle_view_model.bind_property("state", toggle, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - w = view; - } + var toggle_view_model = preferences_row as ViewModel.PreferencesRow.Toggle; + if (toggle_view_model != null) { + var view = new Adw.ActionRow() { title = toggle_view_model.title, subtitle = toggle_view_model.subtitle }; + var toggle = new Switch() { valign = Align.CENTER }; + view.activatable_widget = toggle; + view.add_suffix(toggle); + toggle_view_model.bind_property("state", toggle, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + return view; + } - var combobox_view_model = preferences_row as ViewModel.PreferencesRow.ComboBox; - if (combobox_view_model != null) { - var string_list = new StringList(null); - foreach (string text in combobox_view_model.items) { - string_list.append(text); - } + var combobox_view_model = preferences_row as ViewModel.PreferencesRow.ComboBox; + if (combobox_view_model != null) { + var string_list = new StringList(null); + foreach (string text in combobox_view_model.items) { + string_list.append(text); + } #if Adw_1_4 - var view = new Adw.ComboRow() { title = combobox_view_model.title }; - view.model = string_list; - combobox_view_model.bind_property("active-item", view, "selected", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + var view = new Adw.ComboRow() { title = combobox_view_model.title }; + view.model = string_list; + combobox_view_model.bind_property("active-item", view, "selected", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); #else - var view = new Adw.ActionRow() { title = combobox_view_model.title }; - var drop_down = new DropDown(string_list, null) { valign = Align.CENTER }; - combobox_view_model.bind_property("active-item", drop_down, "selected", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - view.activatable_widget = drop_down; - view.add_suffix(drop_down); + var view = new Adw.ActionRow() { title = combobox_view_model.title }; + var drop_down = new DropDown(string_list, null) { valign = Align.CENTER }; + combobox_view_model.bind_property("active-item", drop_down, "selected", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + view.activatable_widget = drop_down; + view.add_suffix(drop_down); #endif - w = view; - } - - var widget_view_model = preferences_row as ViewModel.PreferencesRow.WidgetDeprecated; - if (widget_view_model != null) { - var view = new Adw.ActionRow() { title = widget_view_model.title }; - view.add_suffix(widget_view_model.widget); - w = view; - } - - if (w == null) { - continue; - } + return view; + } - preference_group.add(w); + var widget_view_model = preferences_row as ViewModel.PreferencesRow.WidgetDeprecated; + if (widget_view_model != null) { + var view = new Adw.ActionRow() { title = widget_view_model.title }; + view.add_suffix(widget_view_model.widget); + return view; } - return preference_group; + return null; } }
\ No newline at end of file diff --git a/main/src/view_model/conversation_details.vala b/main/src/view_model/conversation_details.vala index 75fc9669..3641d584 100644 --- a/main/src/view_model/conversation_details.vala +++ b/main/src/view_model/conversation_details.vala @@ -39,13 +39,42 @@ public class Dino.Ui.ViewModel.ConversationDetails : Object { public bool show_blocked { get; set; } public BlockState blocked { get; set; } - public GLib.ListStore preferences_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); public GLib.ListStore about_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); - public GLib.ListStore encryption_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); public GLib.ListStore settings_rows = new GLib.ListStore(typeof(PreferencesRow.Any)); public GLib.ListStore room_configuration_rows { get; set; } + public MapListModel members = new MapListModel(null, null); + public SortListModel members_sorted = new SortListModel(null, new MucMemberSorter()); + + construct { + members = new MapListModel(members_sorted, null); + } } +public class MucMemberSorter : Sorter { + + public override Gtk.Ordering compare(GLib.Object? item1, GLib.Object? item2) { + var member_list_row1 = (Dino.Ui.Model.ConferenceMember) item1; + var member_list_row2 = (Dino.Ui.Model.ConferenceMember) item2; + var test = new Xmpp.Xep.Muc.Affiliation[] { OWNER, ADMIN, MEMBER }; + var affiliation_ordering = new ArrayList<Xmpp.Xep.Muc.Affiliation>.wrap(test); + + var affiliation_sorting = affiliation_ordering.index_of(member_list_row1.affiliation) - affiliation_ordering.index_of(member_list_row2.affiliation); + if (affiliation_sorting == 0) { + return Ordering.from_cmpfunc(member_list_row1.name.collate(member_list_row2.name)); + } + + return Ordering.from_cmpfunc(affiliation_sorting); + } + + public override Gtk.SorterOrder get_order() { + return SorterOrder.TOTAL; + } +} + +//public class Dino.Ui.ViewModel.ConferenceDetails : Dino.Ui.ViewModel.ConversationDetails { +// public static +//} + public class Dino.Ui.Model.ConversationDetails : Object { public Conversation conversation { get; set; } public Dino.Model.ConversationDisplayName display_name { get; set; } @@ -53,4 +82,54 @@ public class Dino.Ui.Model.ConversationDetails : Object { public string? data_form_bak; public bool blocked { get; set; } public bool domain_blocked { get; set; } + + public GLib.ListStore members = new GLib.ListStore(typeof(Ui.Model.ConferenceMember)); + + public void populate(StreamInteractor stream_interactor, Conversation conversation) { + Ui.ConversationDetails.populate_dialog(this, conversation, stream_interactor); + + if (conversation.type_ == GROUPCHAT) { + Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account); + if (occupants != null) { + foreach (Jid occupant in occupants) { + var affiliation = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, occupant, conversation.account); + members.append(new Dino.Ui.Model.ConferenceMember() { + name = occupant.to_string(), + jid = occupant, + affiliation = affiliation + }); + } + } + } + } +} + +public class Dino.Ui.Model.ConferenceMember : Object { + public string name { get; set; } + public Jid jid { get; set; } + public Xmpp.Xep.Muc.Affiliation affiliation { get; set; } } + +public class Dino.Ui.ViewModel.ConferenceMemberListRow : Object { + public ViewModel.CompatAvatarPictureModel avatar { get; set; } + public string name { get; set; } + public string jid { get; set; } + public Xmpp.Xep.Muc.Affiliation affiliation { get; set; } + public string? affiliation_str { get; set; } + + construct { + this.bind_property("affiliation", this, "affiliation-str", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL, (_, from_value, ref to_value) => { + to_value = affiliation_to_str((Xmpp.Xep.Muc.Affiliation) from_value); + return true; + }); + } + + private string? affiliation_to_str(Xmpp.Xep.Muc.Affiliation affiliation) { + switch (affiliation) { + case OWNER: return _("Owner"); + case ADMIN: return _("Admin"); + case MEMBER: return _("Member"); + default: return null; + } + } +}
\ No newline at end of file diff --git a/main/src/windows/conversation_details.vala b/main/src/windows/conversation_details.vala index c2484db8..5c0c78b3 100644 --- a/main/src/windows/conversation_details.vala +++ b/main/src/windows/conversation_details.vala @@ -8,6 +8,7 @@ namespace Dino.Ui.ConversationDetails { [GtkTemplate (ui = "/im/dino/Dino/conversation_details.ui")] public class Dialog : Adw.Window { + [GtkChild] public unowned Stack stack; [GtkChild] public unowned Box about_box; [GtkChild] public unowned Button pin_button; [GtkChild] public unowned Adw.ButtonContent pin_button_content; @@ -22,6 +23,12 @@ namespace Dino.Ui.ConversationDetails { [GtkChild] public unowned ViewModel.ConversationDetails model { get; } + public StackPage? encryption_stack_page = null; + public Box? encryption_box = null; + + public StackPage? member_stack_page = null; + public Box? member_box = null; + private SimpleAction block_action = new SimpleAction.stateful("block", VariantType.INT32, new Variant.int32(ViewModel.ConversationDetails.BlockState.UNBLOCK)); class construct { @@ -44,10 +51,12 @@ namespace Dino.Ui.ConversationDetails { model.notify["notification-is-default"].connect(update_notification_button_visibility); model.about_rows.items_changed.connect(create_preferences_rows); - model.encryption_rows.items_changed.connect(create_preferences_rows); model.settings_rows.items_changed.connect(create_preferences_rows); model.notify["room-configuration-rows"].connect(create_preferences_rows); + model.notify["members"].connect(create_members); + create_members(); + // Create block action SimpleActionGroup block_action_group = new SimpleActionGroup(); block_action = new SimpleAction.stateful("block", VariantType.INT32, new Variant.int32(0)); @@ -145,6 +154,30 @@ namespace Dino.Ui.ConversationDetails { } } + private void create_members() { + if (model.members_sorted.n_items == 0) return; + + var selection_model = new NoSelection(model.members_sorted); + var item_factory = new BuilderListItemFactory.from_resource(null, "/im/dino/Dino/muc_member_list_row.ui"); + var list_view = new ListView(selection_model, item_factory) { single_click_activate = true }; + list_view.add_css_class("card"); + list_view.activate.connect((position) => { +// var widget = (Gtk.Widget) list_view.observe_children().get_item(position); +// var name_label = widget.get_template_child(Type.OBJECT, "name-label"); +// print(widget.get_type().name()); + +// var popover = new Popover(); +// popover.parent = widget; +// popover.popup(); + + + var row_view_model = (Ui.Model.ConferenceMember) model.members_sorted.get_item(position); + print(@"$(position) $(row_view_model.name)\n"); + }); + + add_members_tab_element(list_view); + } + private void create_preferences_rows() { var widget = about_box.get_first_child(); while (widget != null) { @@ -155,9 +188,6 @@ namespace Dino.Ui.ConversationDetails { if (model.about_rows.get_n_items() > 0) { about_box.append(Util.rows_to_preference_group(model.about_rows, _("About"))); } - if (model.encryption_rows.get_n_items() > 0) { - about_box.append(Util.rows_to_preference_group(model.encryption_rows, _("Encryption"))); - } if (model.settings_rows.get_n_items() > 0) { about_box.append(Util.rows_to_preference_group(model.settings_rows, _("Settings"))); } @@ -165,5 +195,34 @@ namespace Dino.Ui.ConversationDetails { about_box.append(Util.rows_to_preference_group(model.room_configuration_rows, _("Room Configuration"))); } } + + public void add_encryption_tab_element(Adw.PreferencesGroup preferences_group) { + if (encryption_stack_page == null) { + encryption_box = new Box(Orientation.VERTICAL, 12) { margin_end = 12, margin_start = 12, margin_top = 18, margin_bottom = 40 }; + var scrolled_window = new ScrolledWindow() { vexpand = true }; + var clamp = new Adw.Clamp(); + clamp.set_child(encryption_box); + scrolled_window.set_child(clamp); + encryption_stack_page = stack.add_child(scrolled_window); + encryption_stack_page.title = _("Encryption"); + encryption_stack_page.name = "encryption"; + } + encryption_box.append(preferences_group); + } + + public void add_members_tab_element(Widget widget) { + if (member_stack_page == null) { + member_box = new Box(Orientation.VERTICAL, 12) { margin_end = 12, margin_start = 12, margin_top = 18 }; + member_stack_page = stack.add_child(member_box); + member_stack_page.title = _("Members"); + member_stack_page.name = "member"; + } + member_box.append(widget); + } + } + + [GtkTemplate (ui = "/im/dino/Dino/muc_member_list_row.ui")] + public class Dino.Ui.ConversationDetails.MemberListItem : Gtk.Widget { + } } diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index 410d5712..00130863 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -72,7 +72,6 @@ SOURCES src/ui/bad_messages_populator.vala src/ui/call_encryption_entry.vala src/ui/contact_details_provider.vala - src/ui/contact_details_dialog.vala src/ui/device_notification_populator.vala src/ui/own_notifications.vala src/ui/encryption_list_entry.vala diff --git a/plugins/omemo/data/encryption_preferences_entry.ui b/plugins/omemo/data/encryption_preferences_entry.ui index 7ca26224..e84f56b4 100644 --- a/plugins/omemo/data/encryption_preferences_entry.ui +++ b/plugins/omemo/data/encryption_preferences_entry.ui @@ -21,7 +21,9 @@ </object> </child> <child> + <!-- TODO: make this a SwitchRow once we depend on Adwaita 1.4--> <object class="AdwActionRow" id="automatically_accept_new_row"> + <property name="activatable-widget">automatically_accept_new_switch</property> <child type="suffix"> <object class="GtkSwitch" id="automatically_accept_new_switch"> <property name="valign">center</property> diff --git a/plugins/omemo/meson.build b/plugins/omemo/meson.build index 05d7c265..f1fdb46d 100644 --- a/plugins/omemo/meson.build +++ b/plugins/omemo/meson.build @@ -43,7 +43,6 @@ sources = files( 'src/trust_level.vala', 'src/ui/bad_messages_populator.vala', 'src/ui/call_encryption_entry.vala', - 'src/ui/contact_details_dialog.vala', 'src/ui/contact_details_provider.vala', 'src/ui/device_notification_populator.vala', 'src/ui/encryption_list_entry.vala', diff --git a/plugins/omemo/src/plugin.vala b/plugins/omemo/src/plugin.vala index dfbe0780..b6142ba6 100644 --- a/plugins/omemo/src/plugin.vala +++ b/plugins/omemo/src/plugin.vala @@ -72,18 +72,6 @@ public class Plugin : RootInterface, Object { Manager.start(this.app.stream_interactor, db, trust_manager, encryptors); - SimpleAction own_keys_action = new SimpleAction("own-keys", VariantType.INT32); - own_keys_action.activate.connect((variant) => { - foreach(Dino.Entities.Account account in this.app.stream_interactor.get_accounts()) { - if(account.id == variant.get_int32()) { - ContactDetailsDialog dialog = new ContactDetailsDialog(this, account, account.bare_jid); - dialog.set_transient_for(((Gtk.Application) this.app).get_active_window()); - dialog.present(); - } - } - }); - this.app.add_action(own_keys_action); - string locales_dir; if (app.search_path_generator != null) { locales_dir = ((!)app.search_path_generator).get_locale_path(GETTEXT_PACKAGE, LOCALE_INSTALL_DIR); diff --git a/plugins/omemo/src/ui/bad_messages_populator.vala b/plugins/omemo/src/ui/bad_messages_populator.vala index 8f087482..d87108ea 100644 --- a/plugins/omemo/src/ui/bad_messages_populator.vala +++ b/plugins/omemo/src/ui/bad_messages_populator.vala @@ -180,9 +180,8 @@ public class BadMessagesWidget : Box { } private bool on_label_activate_link() { - ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, conversation.account, jid); - dialog.set_transient_for((Window) get_root()); - dialog.present(); + var variant = new Variant.tuple(new Variant[] {new Variant.int32(conversation.id), new Variant.string("encryption")}); + GLib.Application.get_default().activate_action("open-conversation-details", variant); return false; } diff --git a/plugins/omemo/src/ui/contact_details_dialog.vala b/plugins/omemo/src/ui/contact_details_dialog.vala deleted file mode 100644 index 1fddd759..00000000 --- a/plugins/omemo/src/ui/contact_details_dialog.vala +++ /dev/null @@ -1,353 +0,0 @@ -using Gtk; -using Xmpp; -using Gee; -using Qlite; -using Dino.Entities; -using Qrencode; -using Gdk; - -namespace Dino.Plugins.Omemo { - -[GtkTemplate (ui = "/im/dino/Dino/omemo/contact_details_dialog.ui")] -public class ContactDetailsDialog : Gtk.Dialog { - - private Plugin plugin; - private Account account; - private Jid jid; - private bool own = false; - private int own_id = 0; - private int identity_id = 0; - private Signal.Store store; - private Set<uint32> displayed_ids = new HashSet<uint32>(); - - [GtkChild] private unowned Label automatically_accept_new_label; - [GtkChild] private unowned Label automatically_accept_new_descr; - [GtkChild] private unowned Label own_key_label; - [GtkChild] private unowned Label new_keys_label; - [GtkChild] private unowned Label associated_keys_label; - [GtkChild] private unowned Label inactive_expander_label; - - [GtkChild] private unowned Box own_fingerprint_container; - [GtkChild] private unowned Label own_fingerprint_label; - [GtkChild] private unowned Box new_keys_container; - [GtkChild] private unowned ListBox new_keys_listbox; - [GtkChild] private unowned Box keys_container; - [GtkChild] private unowned ListBox keys_listbox; - [GtkChild] private unowned Expander inactive_keys_expander; - [GtkChild] private unowned ListBox inactive_keys_listbox; - [GtkChild] private unowned Switch auto_accept_switch; - [GtkChild] private unowned Button copy_button; - [GtkChild] private unowned MenuButton show_qrcode_button; - [GtkChild] private unowned Picture qrcode_picture; - [GtkChild] private unowned Popover qrcode_popover; - - private ArrayList<Widget> new_keys_listbox_children = new ArrayList<Widget>(); - - construct { - // If we set the strings in the .ui file, they don't get translated - title = _("OMEMO Key Management"); - automatically_accept_new_label.label = _("Automatically accept new keys"); - automatically_accept_new_descr.label = _("New encryption keys from this contact will be accepted automatically."); - own_key_label.label = _("Own key"); - new_keys_label.label = _("New keys"); - associated_keys_label.label = _("Associated keys"); - inactive_expander_label.label = _("Inactive keys"); - } - - public ContactDetailsDialog(Plugin plugin, Account account, Jid jid) { - Object(use_header_bar : 1); - this.plugin = plugin; - this.account = account; - this.jid = jid; - - keys_listbox.row_activated.connect(on_key_entry_clicked); - inactive_keys_listbox.row_activated.connect(on_key_entry_clicked); - auto_accept_switch.state_set.connect(on_auto_accept_toggled); - - identity_id = plugin.db.identity.get_id(account.id); - if (identity_id < 0) return; - Dino.Application? app = Application.get_default() as Dino.Application; - if (app != null) { - store = app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).store; - } - - auto_accept_switch.set_active(plugin.db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string(), true)); - - // Dialog opened from the account settings menu - // Show the fingerprint for this device separately with buttons for a qrcode and to copy - if(jid.equals(account.bare_jid)) { - own = true; - own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; - - automatically_accept_new_descr.label = _("New encryption keys from your other devices will be accepted automatically."); - - own_fingerprint_container.visible = true; - - string own_b64 = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.identity_key_public_base64]; - string fingerprint = fingerprint_from_base64(own_b64); - own_fingerprint_label.set_markup(fingerprint_markup(fingerprint)); - - copy_button.clicked.connect(() => { copy_button.get_clipboard().set_text(fingerprint); }); - - int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; - var iri_query = @"omemo-sid-$(sid)=$(fingerprint)"; -#if GLIB_2_66 && VALA_0_50 - string iri = GLib.Uri.join(UriFlags.NONE, "xmpp", null, null, 0, jid.to_string(), iri_query, null); -#else - var iri_path_seg = escape_for_iri_path_segment(jid.to_string()); - var iri = @"xmpp:$(iri_path_seg)?$(iri_query)"; -#endif - - const int QUIET_ZONE_MODULES = 4; // MUST be at least 4 - const int MODULE_SIZE_PX = 4; // arbitrary - var qr_paintable = new QRcode(iri, 2) - .to_paintable(MODULE_SIZE_PX * qrcode_picture.scale_factor); - qrcode_picture.paintable = qr_paintable; - qrcode_picture.margin_top = qrcode_picture.margin_end = - qrcode_picture.margin_bottom = qrcode_picture.margin_start = QUIET_ZONE_MODULES * MODULE_SIZE_PX; - qrcode_popover.add_css_class("qrcode-container"); - - show_qrcode_button.popover = qrcode_popover; - } - - new_keys_listbox.set_header_func(header_function); - - keys_listbox.set_header_func(header_function); - - //Show any new devices for which the user must decide whether to accept or reject - foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) { - add_new_fingerprint(device); - } - - //Show the normal devicelist - foreach (Row device in plugin.db.identity_meta.get_known_devices(identity_id, jid.to_string())) { - if(own && device[plugin.db.identity_meta.device_id] == own_id) { - continue; - } - add_fingerprint(device, (TrustLevel) device[plugin.db.identity_meta.trust_level]); - } - - // Check for unknown devices - fetch_unknown_bundles(); - } - - private static string escape_for_iri_path_segment(string s) { - // from RFC 3986, 2.2. Reserved Characters: - string SUB_DELIMS = "!$&'()*+,;="; - // from RFC 3986, 3.3. Path (pchar without unreserved and pct-encoded): - string ALLOWED_RESERVED_CHARS = SUB_DELIMS + ":@"; - return GLib.Uri.escape_string(s, ALLOWED_RESERVED_CHARS, true); - } - - private void fetch_unknown_bundles() { - Dino.Application app = Application.get_default() as Dino.Application; - XmppStream? stream = app.stream_interactor.get_stream(account); - if (stream == null) return; - StreamModule? module = stream.get_module(StreamModule.IDENTITY); - if (module == null) return; - module.bundle_fetched.connect_after((bundle_jid, device_id, bundle) => { - if (bundle_jid.equals(jid) && !displayed_ids.contains(device_id)) { - Row? device = plugin.db.identity_meta.get_device(identity_id, jid.to_string(), device_id); - if (device == null) return; - if (auto_accept_switch.active) { - add_fingerprint(device, (TrustLevel) device[plugin.db.identity_meta.trust_level]); - } else { - add_new_fingerprint(device); - } - } - }); - foreach (Row device in plugin.db.identity_meta.get_unknown_devices(identity_id, jid.to_string())) { - try { - module.fetch_bundle(stream, new Jid(device[plugin.db.identity_meta.address_name]), device[plugin.db.identity_meta.device_id], false); - } catch (InvalidJidError e) { - warning("Ignoring device with invalid Jid: %s", e.message); - } - } - } - - private void header_function(ListBoxRow row, ListBoxRow? before) { - if (row.get_header() == null && before != null) { - row.set_header(new Separator(Orientation.HORIZONTAL)); - } - } - - private void add_fingerprint(Row device, TrustLevel trust) { - string key_base64 = device[plugin.db.identity_meta.identity_key_public_base64]; - bool key_active = device[plugin.db.identity_meta.now_active]; - if (store != null) { - try { - Signal.Address address = new Signal.Address(jid.to_string(), device[plugin.db.identity_meta.device_id]); - Signal.SessionRecord? session = null; - if (store.contains_session(address)) { - session = store.load_session(address); - string session_key_base64 = Base64.encode(session.state.remote_identity_key.serialize()); - if (key_base64 != session_key_base64) { - critical("Session and database identity key mismatch!"); - key_base64 = session_key_base64; - } - } - } catch (Error e) { - print("Error while reading session store: %s", e.message); - } - } - FingerprintRow fingerprint_row = new FingerprintRow(device, key_base64, trust, key_active) { visible = true, activatable = true, hexpand = true }; - - if (device[plugin.db.identity_meta.now_active]) { - keys_container.visible = true; - keys_listbox.append(fingerprint_row); - } else { - inactive_keys_expander.visible=true; - inactive_keys_listbox.append(fingerprint_row); - } - displayed_ids.add(device[plugin.db.identity_meta.device_id]); - } - - private void on_key_entry_clicked(ListBoxRow widget) { - FingerprintRow? fingerprint_row = widget as FingerprintRow; - if (fingerprint_row == null) return; - - Row updated_device = plugin.db.identity_meta.get_device(fingerprint_row.row[plugin.db.identity_meta.identity_id], fingerprint_row.row[plugin.db.identity_meta.address_name], fingerprint_row.row[plugin.db.identity_meta.device_id]); - ManageKeyDialog manage_dialog = new ManageKeyDialog(updated_device, plugin.db); - manage_dialog.set_transient_for((Gtk.Window) get_root()); - manage_dialog.present(); - manage_dialog.response.connect((response) => { - fingerprint_row.update_trust_state(response, fingerprint_row.row[plugin.db.identity_meta.now_active]); - update_stored_trust(response, fingerprint_row.row); - }); - } - - private bool on_auto_accept_toggled(bool active) { - plugin.trust_manager.set_blind_trust(account, jid, active); - - if (active) { - int identity_id = plugin.db.identity.get_id(account.id); - if (identity_id < 0) return false; - - new_keys_container.visible = false; - foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) { - plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); - add_fingerprint(device, TrustLevel.TRUSTED); - } - } - return false; - } - - private void update_stored_trust(int response, Row device) { - switch (response) { - case TrustLevel.TRUSTED: - plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); - break; - case TrustLevel.UNTRUSTED: - plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); - break; - case TrustLevel.VERIFIED: - plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.VERIFIED); - plugin.trust_manager.set_blind_trust(account, jid, false); - auto_accept_switch.set_active(false); - break; - } - } - - private void add_new_fingerprint(Row device) { - new_keys_container.visible = true; - - ListBoxRow lbr = new ListBoxRow() { visible = true, activatable = false, hexpand = true }; - Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true }; - - Button accept_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; - accept_button.set_icon_name("emblem-ok-symbolic"); // using .image = sets .image-button. Together with .suggested/destructive action that breaks the button Adwaita - accept_button.add_css_class("suggested-action"); - accept_button.tooltip_text = _("Accept key"); - - Button reject_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; - reject_button.set_icon_name("action-unavailable-symbolic"); - reject_button.add_css_class("destructive-action"); - reject_button.tooltip_text = _("Reject key"); - - accept_button.clicked.connect(() => { - plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); - add_fingerprint(device, TrustLevel.TRUSTED); - new_keys_listbox.remove(lbr); - new_keys_listbox_children.remove(lbr); - if (new_keys_listbox_children.size < 1) new_keys_container.visible = false; - }); - - reject_button.clicked.connect(() => { - plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); - add_fingerprint(device, TrustLevel.UNTRUSTED); - new_keys_listbox.remove(lbr); - new_keys_listbox_children.remove(lbr); - if (new_keys_listbox_children.size < 1) new_keys_container.visible = false; - }); - - string res = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64])); - Label fingerprint_label = new Label(res) { use_markup=true, justify=Justification.RIGHT, halign = Align.START, valign = Align.CENTER, hexpand = false }; - box.append(fingerprint_label); - - Box control_box = new Box(Gtk.Orientation.HORIZONTAL, 0) { visible = true, hexpand = true }; - control_box.append(accept_button); - control_box.append(reject_button); - control_box.add_css_class("linked"); // .linked: Visually link the accept / reject buttons - box.append(control_box); - - lbr.set_child(box); - new_keys_listbox.append(lbr); - new_keys_listbox_children.add(lbr); - displayed_ids.add(device[plugin.db.identity_meta.device_id]); - } -} - -public class FingerprintRow : ListBoxRow { - - private Image trust_image = new Image() { visible = true, halign = Align.END }; - private Label fingerprint_label = new Label("") { use_markup=true, justify=Justification.RIGHT, halign = Align.START, valign = Align.CENTER, hexpand = false }; - private Label trust_label = new Label(null) { visible = true, hexpand = true, xalign = 0 }; - - public Row row; - - construct { - Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true }; - Box status_box = new Box(Gtk.Orientation.HORIZONTAL, 5) { visible = true, hexpand = true }; - - box.append(fingerprint_label); - box.append(status_box); - - status_box.append(trust_label); - status_box.append(trust_image); - - this.set_child(box); - } - - public FingerprintRow(Row row, string key_base64, int trust, bool now_active) { - this.row = row; - fingerprint_label.label = fingerprint_markup(fingerprint_from_base64(key_base64)); - update_trust_state(trust, now_active); - } - - public void update_trust_state(int trust, bool now_active) { - switch(trust) { - case TrustLevel.TRUSTED: - trust_image.icon_name = "emblem-ok-symbolic"; - trust_label.set_markup("<span color='#1A63D9'>%s</span>".printf(_("Accepted"))); - fingerprint_label.remove_css_class("dim-label"); - break; - case TrustLevel.UNTRUSTED: - trust_image.icon_name = "action-unavailable-symbolic"; - trust_label.set_markup("<span color='#D91900'>%s</span>".printf(_("Rejected"))); - fingerprint_label.add_css_class("dim-label"); - break; - case TrustLevel.VERIFIED: - trust_image.icon_name = "security-high-symbolic"; - trust_label.set_markup("<span color='#1A63D9'>%s</span>".printf(_("Verified"))); - fingerprint_label.remove_css_class("dim-label"); - break; - } - - if (!now_active) { - trust_image.icon_name = "appointment-missed-symbolic"; - trust_label.set_markup("<span color='#8b8e8f'>%s</span>".printf(_("Unused"))); - } - } -} - -} diff --git a/plugins/omemo/src/ui/contact_details_provider.vala b/plugins/omemo/src/ui/contact_details_provider.vala index a97a40ad..7c733cd7 100644 --- a/plugins/omemo/src/ui/contact_details_provider.vala +++ b/plugins/omemo/src/ui/contact_details_provider.vala @@ -7,6 +7,7 @@ namespace Dino.Plugins.Omemo { public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "omemo_info"; } } + public string tab { get { return "encryption"; } } private Plugin plugin; @@ -14,35 +15,14 @@ public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object { this.plugin = plugin; } - public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) { - if (conversation.type_ == Conversation.Type.CHAT && type == WidgetType.GTK4) { - - int identity_id = plugin.db.identity.get_id(conversation.account.id); - if (identity_id < 0) return; - - int i = 0; - foreach (Row row in plugin.db.identity_meta.with_address(identity_id, conversation.counterpart.to_string())) { - if (row[plugin.db.identity_meta.identity_key_public_base64] != null) { - i++; - } - } - - if (i > 0) { - Button btn = new Button.from_icon_name("view-list-symbolic") { visible = true, valign = Align.CENTER, has_frame = false }; - btn.tooltip_text = _("OMEMO Key Management"); - btn.clicked.connect(() => { - btn.activate(); - ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, conversation.account, conversation.counterpart); - dialog.set_transient_for((Window) btn.get_root()); - dialog.response.connect((response_type) => { - plugin.device_notification_populator.should_hide(); - }); - dialog.present(); - }); - - contact_details.add(_("Encryption"), "OMEMO", n("%d OMEMO device", "%d OMEMO devices", i).printf(i), btn); - } - } + public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) { } + + public Object? get_widget(Conversation conversation) { + if (conversation.type_ != Conversation.Type.CHAT) return null; + + var widget = new OmemoPreferencesWidget(plugin); + widget.set_jid(conversation.account, conversation.counterpart); + return widget; } } diff --git a/plugins/omemo/src/ui/device_notification_populator.vala b/plugins/omemo/src/ui/device_notification_populator.vala index fafe7a24..7829d89c 100644 --- a/plugins/omemo/src/ui/device_notification_populator.vala +++ b/plugins/omemo/src/ui/device_notification_populator.vala @@ -35,7 +35,7 @@ public class DeviceNotificationPopulator : NotificationPopulator, Object { private void display_notification() { if (notification == null) { - notification = new ConversationNotification(plugin, current_conversation.account, current_conversation.counterpart); + notification = new ConversationNotification(plugin, current_conversation); notification.should_hide.connect(should_hide); notification_collection.add_meta_notification(notification); } @@ -64,7 +64,7 @@ private class ConversationNotification : MetaConversationNotification { private Account account; public signal void should_hide(); - public ConversationNotification(Plugin plugin, Account account, Jid jid) { + public ConversationNotification(Plugin plugin, Conversation conversation) { this.plugin = plugin; this.jid = jid; this.account = account; @@ -73,12 +73,9 @@ private class ConversationNotification : MetaConversationNotification { Button manage_button = new Button.with_label(_("Manage")); manage_button.clicked.connect(() => { manage_button.activate(); - ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, account, jid); - dialog.set_transient_for((Window) manage_button.get_root()); - dialog.response.connect((response_type) => { - should_hide(); - }); - dialog.present(); + + var variant = new Variant.tuple(new Variant[] {new Variant.int32(conversation.id), new Variant.string("encryption")}); + GLib.Application.get_default().activate_action("open-conversation-details", variant); }); box.append(new Label(_("This contact has new devices")) { margin_end=10 }); box.append(manage_button); diff --git a/plugins/omemo/src/ui/encryption_preferences_entry.vala b/plugins/omemo/src/ui/encryption_preferences_entry.vala index 71cd68ec..0f9456d9 100644 --- a/plugins/omemo/src/ui/encryption_preferences_entry.vala +++ b/plugins/omemo/src/ui/encryption_preferences_entry.vala @@ -19,7 +19,7 @@ public class OmemoPreferencesEntry : Plugins.EncryptionPreferencesEntry { public override Object? get_widget(Account account, WidgetType type) { if (type != WidgetType.GTK4) return null; var widget = new OmemoPreferencesWidget(plugin); - widget.set_account(account); + widget.set_jid(account, account.bare_jid); return widget; } @@ -59,22 +59,24 @@ public class OmemoPreferencesWidget : Adw.PreferencesGroup { public OmemoPreferencesWidget(Plugin plugin) { this.plugin = plugin; - this.account = account; - this.jid = jid; } - public void set_account(Account account) { + public void set_jid(Account account, Jid jid) { this.account = account; - this.jid = account.bare_jid; + this.jid = jid; + this.identity_id = plugin.db.identity.get_id(account.id); + if (identity_id <= 0) { + warning("OmemoPreferencesWidget missing identity_id"); + return; + } automatically_accept_new_switch.set_active(plugin.db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string(), true)); automatically_accept_new_switch.state_set.connect(on_auto_accept_toggled); + encrypt_by_default_row.visible = account.bare_jid.equals_bare(jid); encrypt_by_default_switch.set_active(plugin.app.settings.get_default_encryption(account) != Encryption.NONE); encrypt_by_default_switch.state_set.connect(on_omemo_by_default_toggled); - identity_id = plugin.db.identity.get_id(account.id); - if (identity_id < 0) return; Dino.Application? app = Application.get_default() as Dino.Application; if (app != null) { store = app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).store; @@ -216,7 +218,7 @@ public class OmemoPreferencesWidget : Adw.PreferencesGroup { }); }); action_row.activatable = true; - action_row.title = "Other device"; + action_row.title = account.bare_jid.equals_bare(jid) ? "Other device" : "Device"; action_row.subtitle = fingerprint_markup(fingerprint_from_base64(key_base64)); string trust_str = _("Accepted"); switch(trust) { diff --git a/plugins/openpgp/src/contact_details_provider.vala b/plugins/openpgp/src/contact_details_provider.vala index 9ec84c21..f2fa8f06 100644 --- a/plugins/openpgp/src/contact_details_provider.vala +++ b/plugins/openpgp/src/contact_details_provider.vala @@ -6,6 +6,7 @@ namespace Dino.Plugins.OpenPgp { public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "pgp_info"; } } + public string tab { get { return "encryption"; } } private StreamInteractor stream_interactor; @@ -13,23 +14,39 @@ public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object { this.stream_interactor = stream_interactor; } - public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) { - if (conversation.type_ == Conversation.Type.CHAT && type == WidgetType.GTK4) { - string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, conversation.counterpart); - if (key_id != null) { - Label label = new Label("") { use_markup=true, justify=Justification.RIGHT, selectable=true }; - Gee.List<GPG.Key>? keys = null; - try { - keys = GPGHelper.get_keylist(key_id); - } catch (Error e) { } - if (keys != null && keys.size > 0) { - label.label = markup_colorize_id(keys[0].fpr, true); - } else { - label.label = _("Key not in keychain") + "\n" + markup_colorize_id(key_id, false); - } - contact_details.add(_("Encryption"), "OpenPGP", "", label); - } + public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) { } + + public Object? get_widget(Conversation conversation) { + var preferences_group = new Adw.PreferencesGroup() { title="OpenPGP" }; + + if (conversation.type_ != Conversation.Type.CHAT) return null; + + string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, conversation.counterpart); + if (key_id == null) return null; + + Gee.List<GPG.Key>? keys = null; + try { + keys = GPGHelper.get_keylist(key_id); + } catch (Error e) { } + + var str = ""; + if (keys != null && keys.size > 0) { + str = markup_id(keys[0].fpr, true); + } else { + str = _("Key not in keychain") + "\n" + markup_id(key_id, false); } + + var view = new Adw.ActionRow() { + title = "Fingerprint", + subtitle = str, +#if Adw_1_3 + subtitle_selectable = true, +#endif + }; + + preferences_group.add(view); + + return preferences_group; } } diff --git a/plugins/openpgp/src/util.vala b/plugins/openpgp/src/util.vala index d40cf6ef..9cba9b2f 100644 --- a/plugins/openpgp/src/util.vala +++ b/plugins/openpgp/src/util.vala @@ -5,47 +5,16 @@ using Xmpp.Util; namespace Dino.Plugins.OpenPgp { -/* Adapted from OpenKeychain */ -public static string markup_colorize_id(string s, bool is_fingerprint) { +public static string markup_id(string s, bool is_fingerprint) { string markup = is_fingerprint ? "" : "0x"; for (int i = 0; i < s.length; i += 4) { string four_chars = s.substring(i, 4).down(); - int raw = (int) from_hex(four_chars); - uint8[] bytes = {(uint8) ((raw >> 8) & 0xff - 128), (uint8) (raw & 0xff - 128)}; - - Checksum checksum = new Checksum(ChecksumType.SHA1); - checksum.update(bytes, bytes.length); - uint8[] digest = new uint8[20]; - size_t len = 20; - checksum.get_digest(digest, ref len); - - uint8 r = digest[0]; - uint8 g = digest[1]; - uint8 b = digest[2]; - - if (r == 0 && g == 0 && b == 0) r = g = b = 1; - - double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; - - if (brightness < 80) { - double factor = 80.0 / brightness; - r = uint8.min(255, (uint8) (r * factor)); - g = uint8.min(255, (uint8) (g * factor)); - b = uint8.min(255, (uint8) (b * factor)); - - } else if (brightness > 180) { - double factor = 180.0 / brightness; - r = (uint8) (r * factor); - g = (uint8) (g * factor); - b = (uint8) (b * factor); - } - if (i == 4 * 5) markup += "\n"; - markup += @"<span foreground=\"$("#%02x%02x%02x".printf(r, g, b))\">$four_chars</span>"; + markup += four_chars; if (is_fingerprint) markup += " "; } - return "<span font_family='monospace' font='8'>" + markup + "</span>"; + return "<span font_family='monospace' font='9'>" + markup + "</span>"; } } |