From 928c14f66638d467df98c7382c42f1ff70339aa6 Mon Sep 17 00:00:00 2001 From: alaraajavamma Date: Sat, 22 Aug 2026 00:36:12 +0300 Subject: [PATCH] feat: give group participants room, search and copy A participant card put the name, the number and three buttons in one row, so the name was capped at fifteen characters to make space. The text gets the full width now and the buttons sit under it, which also makes room for two actions the call details already had and the group sheet did not: searching the sender online and copying the number. Both work for alphanumeric senders too, where searching is at its most useful and calling is impossible. The card carries no margins of its own, because the app's card style already pads twelve pixels and every margin added here stacked on top of it. The search buttons made the settings dishonest, so they are split by what they govern. The switch that claimed to control the search button in the in-call window and call history only ever controlled the in-call one, and it hid the engine choice with it, leaving the engine unreachable from the surfaces that used it regardless of the switch. The engine moved to its own Number Lookup category, visible from the calls and the messages app alike, because the buttons it serves are in both while the screening options remain call things. Buttons inside menus the user opens are not gated at all: a feature behind a switch is a feature nobody finds, and the in-call button keeps its opt-in because it is the one that appears uninvited. The participants panel and the search bar slide in from the chat header rather than floating over the list, so nothing dismissed them; a tap in the conversation now does, left unclaimed so it still does whatever it was aimed at. A search with text in it stays, since tapping the filtered list is how a result is reached. --- src/telephony/client/ui/main_window.py | 4 +- src/telephony/client/ui/views/chat_view.py | 59 +++++++++++++---- .../client/ui/windows/settings_window.py | 66 +++++++++++-------- 3 files changed, 88 insertions(+), 41 deletions(-) diff --git a/src/telephony/client/ui/main_window.py b/src/telephony/client/ui/main_window.py index 2540818..f92515c 100644 --- a/src/telephony/client/ui/main_window.py +++ b/src/telephony/client/ui/main_window.py @@ -893,7 +893,7 @@ def _unblock(): if not item.is_saved: add_action(_("Add to Existing Contact"), lambda: self.on_add_to_existing(item), needs_eds=True, opens_flow=True) - add_action(_("Search Number"), lambda: self._search_number_online(item.number)) + add_action(_("Search Number"), lambda: self.search_number_online(item.number)) add_action(_("Send Message"), lambda: self.present_chat(item.number)) add_action(_("Copy Number"), lambda: self.copy_to_clipboard(item.number)) @@ -914,7 +914,7 @@ def _unblock(): present_sheet_page(self, Adw.NavigationPage(title=_("Call Details"), child=toolbar)) - def _search_number_online(self, number): + def search_number_online(self, number): """Open the configured search engine for a phone number.""" clean_num = number.replace("+", "") engine = self.gsettings_mgr.get_setting("unknown_callers_engine") or "duckduckgo" diff --git a/src/telephony/client/ui/views/chat_view.py b/src/telephony/client/ui/views/chat_view.py index 4991e27..ebc0b6a 100644 --- a/src/telephony/client/ui/views/chat_view.py +++ b/src/telephony/client/ui/views/chat_view.py @@ -210,6 +210,24 @@ def _on_btn_menu_toggled(btn): self.list_view = Gtk.ListView(model=self.selection, factory=factory) self.scrolled = Gtk.ScrolledWindow(child=self.list_view, vexpand=True) + dismiss = Gtk.GestureClick() + dismiss.set_propagation_phase(Gtk.PropagationPhase.CAPTURE) + + def _dismiss_top_panels(_gesture, _n_press, _x, _y): + """Close the details panel and the search bar on a tap in the chat. + + They slide in from the header rather than floating over the + list, so nothing dismisses them for us; the tap is left + unclaimed and still does whatever it was aimed at. + """ + if self.btn_menu.get_active(): + self.btn_menu.set_active(False) + if self.btn_search.get_active() and not self.search_entry.get_text(): + self.btn_search.set_active(False) + + dismiss.connect("pressed", _dismiss_top_panels) + self.scrolled.add_controller(dismiss) + self.scrolled.add_css_class("inverted-list") self.chat_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) @@ -696,17 +714,16 @@ def setup_details_panel(self): scrolled.set_max_content_height(350) scrolled.set_propagate_natural_height(True) - list_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) + list_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) block_buttons = {} dialing_ok = self.app_window.ofono.dialing_available() if self.app_window.ofono else True for rec in self.recipients: - hbox = Gtk.Box(spacing=12, css_classes=["card"]) - hbox.set_margin_start(8) - hbox.set_margin_end(8) - hbox.set_margin_top(8) - hbox.set_margin_bottom(8) + card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, css_classes=["card"]) + + inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + card.append(inner) name = self.app_window.eds.get_display_name(rec) if not name: @@ -717,21 +734,19 @@ def setup_details_panel(self): elif name == "Unknown": name = _("Unknown") - vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) lbl_name = Gtk.Label(label=name, xalign=0, css_classes=["body", "emphasized"]) lbl_name.set_ellipsize(Pango.EllipsizeMode.END) - lbl_name.set_max_width_chars(15) vbox.append(lbl_name) lbl_num = Gtk.Label(label=rec, xalign=0, css_classes=["caption", "dim-label"]) lbl_num.set_ellipsize(Pango.EllipsizeMode.END) vbox.append(lbl_num) - hbox.append(vbox) - hbox.append(Gtk.Box(hexpand=True)) + inner.append(vbox) - actions_box = Gtk.Box(spacing=12) + actions_box = Gtk.Box(spacing=8, halign=Gtk.Align.START) is_saved = self.app_window.eds.get_contact_name(rec) is not None icon_edit = "document-edit-symbolic" if is_saved else "contact-new-symbolic" @@ -757,6 +772,24 @@ def _handle_edit_click(n=rec): self._recipient_call_btns.append(b_call) b_call.connect("clicked", lambda b, n=rec: GLib.idle_add(lambda: self.app_window.start_call(n) or False)) + b_search = Gtk.Button(icon_name="system-search-symbolic", css_classes=["circular"]) + b_search.set_valign(Gtk.Align.CENTER) + actions_box.append(b_search) + + def _handle_search_click(n=rec): + self.btn_menu.set_active(False) + self.app_window.search_number_online(n) + b_search.connect("clicked", lambda b, n=rec: _handle_search_click(n)) + + b_copy = Gtk.Button(icon_name="edit-copy-symbolic", css_classes=["circular"]) + b_copy.set_valign(Gtk.Align.CENTER) + actions_box.append(b_copy) + + def _handle_copy_click(n=rec): + self.btn_menu.set_active(False) + self.app_window.copy_to_clipboard(n) + b_copy.connect("clicked", lambda b, n=rec: _handle_copy_click(n)) + b_blk = Gtk.Button(icon_name="action-unavailable-symbolic", css_classes=["circular", "destructive-action"]) b_blk.set_valign(Gtk.Align.CENTER) b_blk.set_sensitive(False) @@ -789,8 +822,8 @@ def _after_toggle(did_unblock, target_btn=btn): b_blk.connect("clicked", lambda b, n=rec: _toggle_block(b, n)) - hbox.append(actions_box) - list_box.append(hbox) + inner.append(actions_box) + list_box.append(card) def _fetch_block_states(): return {num: self.db.is_blocked(num) for num in block_buttons} diff --git a/src/telephony/client/ui/windows/settings_window.py b/src/telephony/client/ui/windows/settings_window.py index 040a5aa..80d113f 100644 --- a/src/telephony/client/ui/windows/settings_window.py +++ b/src/telephony/client/ui/windows/settings_window.py @@ -124,9 +124,13 @@ def __init__(self, main_window, eds_manager, ofono_manager): lambda: self._push_category(_("Notifications"), self._build_notifications_page), icon="audio-volume-high-symbolic")) if self.mode_calls: - grp_cats.add(self._nav_row(_("Unknown Callers"), _("Screening and lookup"), + grp_cats.add(self._nav_row(_("Unknown Callers"), _("Screening"), lambda: self._push_category(_("Unknown Callers"), self._build_unknown_callers_page), icon="dialog-question-symbolic")) + if self.mode_calls or self.mode_messages: + grp_cats.add(self._nav_row(_("Number Lookup"), _("Search engine for the search buttons"), + lambda: self._push_category(_("Number Lookup"), self._build_lookup_page), + icon="system-search-symbolic")) grp_cats.add(self._nav_row(_("Network Services"), _("Forwarding, waiting and barring"), lambda: self._open_network_services(None), icon="network-cellular-signal-good-symbolic")) @@ -750,10 +754,39 @@ def _build_unknown_callers_page(self, page): grp_uc.add(self.row_uc_action) self.sw_uc_search = Adw.SwitchRow(title=_( - "Add Search button to InCall window and Call History for unknown callers")) + "Search button for unknown callers during a call")) self.sw_uc_search.set_title_lines(0) grp_uc.add(self.sw_uc_search) + def _on_search_toggle(w, p): + self.main_window.gsettings_mgr.set_setting( + "unknown_callers_search", "true" if w.get_active() else "false") + self.sw_uc_search.connect("notify::active", _on_search_toggle) + + uc_action = self.main_window.gsettings_mgr.get_setting( + "unknown_callers") or "none" + action_idx = next( + (i for i, (k, act_name) in enumerate(self.action_options) if k == uc_action), 0) + set_selector_options(self.row_uc_action, + [name for _key, name in self.action_options], action_idx) + + self.sw_uc_search.set_active(self.main_window.gsettings_mgr.get_setting( + "unknown_callers_search") == "true") + + def _build_lookup_page(self, page): + """Build the number lookup category page. + + Its own category rather than a corner of Unknown Callers, + because the buttons it serves are in both apps while the + screening options are call things, and an engine choice hidden + behind a call switch was unreachable from the surfaces that + used it. + """ + grp_lookup = Adw.PreferencesGroup() + grp_lookup.set_description(_( + "Used by the search buttons in calls, call history and group messages.")) + page.add(grp_lookup) + self.row_uc_engine = build_selector_row( _("Search Engine"), self._on_uc_engine_selected) self.engine_options = [ @@ -769,24 +802,8 @@ def _build_unknown_callers_page(self, page): btn_uc_info = self._info_button(self._show_custom_url_info) self.entry_uc_custom.add_suffix(btn_uc_info) - grp_uc.add(self.row_uc_engine) - grp_uc.add(self.entry_uc_custom) - - def _on_search_toggle(w, p): - self.main_window.gsettings_mgr.set_setting( - "unknown_callers_search", "true" if w.get_active() else "false") - self._update_uc_ui() - self.sw_uc_search.connect("notify::active", _on_search_toggle) - - uc_action = self.main_window.gsettings_mgr.get_setting( - "unknown_callers") or "none" - action_idx = next( - (i for i, (k, act_name) in enumerate(self.action_options) if k == uc_action), 0) - set_selector_options(self.row_uc_action, - [name for _key, name in self.action_options], action_idx) - - self.sw_uc_search.set_active(self.main_window.gsettings_mgr.get_setting( - "unknown_callers_search") == "true") + grp_lookup.add(self.row_uc_engine) + grp_lookup.add(self.entry_uc_custom) engine = self.main_window.gsettings_mgr.get_setting( "unknown_callers_engine") or "duckduckgo" @@ -830,13 +847,10 @@ def _persist_sources(self): run_in_background(self.eds.set_default_addressbook, default['uid']) def _update_uc_ui(self): - search_active = self.sw_uc_search.get_active() - self.row_uc_engine.set_visible(search_active) + """Show the custom URL field only while the custom engine is chosen.""" idx = self.row_uc_engine._selected_index - if search_active and idx >= 0 and self.engine_options[idx][0] == "custom": - self.entry_uc_custom.set_visible(True) - else: - self.entry_uc_custom.set_visible(False) + self.entry_uc_custom.set_visible( + idx >= 0 and self.engine_options[idx][0] == "custom") def _get_gsettings_emergency(self): """Get emergency button setting via Gio.Settings."""