From 165f480d7ea7929eef464c524366f1c3e8d63bda Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 29 Oct 2025 02:08:27 +0100 Subject: [PATCH] Conversation list refactoring --- sbapp/main.py | 9 +- sbapp/ui/conversations.py | 903 +++++++++++++++++++++++++------------- 2 files changed, 601 insertions(+), 311 deletions(-) diff --git a/sbapp/main.py b/sbapp/main.py index 187e99c..c5dbc7e 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -2704,11 +2704,10 @@ class SidebandApp(MDApp): if not self.conversations_view: self.conversations_view = Conversations(self) - for child in self.conversations_view.ids.conversations_scrollview.children: - self.conversations_view.ids.conversations_scrollview.remove_widget(child) - - self.conversations_view.ids.conversations_scrollview.effect_cls = ScrollEffect - self.conversations_view.ids.conversations_scrollview.add_widget(self.conversations_view.get_widget()) + # for child in self.conversations_view.ids.conversations_scrollview.children: + # self.conversations_view.ids.conversations_scrollview.remove_widget(child) + # self.conversations_view.ids.conversations_scrollview.effect_cls = ScrollEffect + # self.conversations_view.ids.conversations_scrollview.add_widget(self.conversations_view.get_widget()) self.root.ids.screen_manager.current = "conversations_screen" if self.messages_view: diff --git a/sbapp/ui/conversations.py b/sbapp/ui/conversations.py index ac263a5..502a63c 100644 --- a/sbapp/ui/conversations.py +++ b/sbapp/ui/conversations.py @@ -3,8 +3,9 @@ import time from kivy.metrics import dp,sp from kivy.uix.boxlayout import BoxLayout -from kivy.properties import StringProperty, BooleanProperty +from kivy.properties import StringProperty, BooleanProperty, OptionProperty, ColorProperty, Property from kivymd.uix.list import MDList, IconLeftWidget, IconRightWidget, OneLineAvatarIconListItem +from kivymd.uix.recycleview import MDRecycleView from kivymd.uix.menu import MDDropdownMenu from kivymd.toast import toast from kivy.uix.gridlayout import GridLayout @@ -18,6 +19,15 @@ from kivymd.uix.dialog import MDDialog from kivy.lang.builder import Builder +theme_text_color_options = ( + "Primary", + "Secondary", + "Hint", + "Error", + "Custom", + "ContrastParentBackground", +) + from kivy.utils import escape_markup if RNS.vendor.platformutils.get_platform() == "android": from ui.helpers import multilingual_markup @@ -39,12 +49,209 @@ class ConvSettings(BoxLayout): is_object = BooleanProperty() ptt_enabled = BooleanProperty() +class ConversationEntry(OneLineAvatarIconListItem): + app = None + owner_screen = None + conversation_dropdown = None + voice_dropdown = None + clear_dialog = None + clear_telemetry_dialog = None + delete_dialog = None + + icon = StringProperty() + ti_color = OptionProperty(None, options=theme_text_color_options) + icon_fg = Property(None, allownone=True) + icon_bg = Property(None, allownone=True) + + def __init__(self): + super().__init__() + self.bind(on_release=self.app.conversation_action) + self.ids.left_icon.bind(on_release=self.left_icon_action) + self.ids.right_icon.bind(on_release=self.right_icon_action) + self.__init_menus() + + def __init_menus(self): + dmi_h = 40 + if ConversationEntry.voice_dropdown == None: + dmv_items = [ { "viewclass": "OneLineListItem", "text": "Edit", "height": dp(dmi_h), "on_release": self.edit_action}, + { "text": "Copy Identity Hash", "viewclass": "OneLineListItem", "height": dp(dmi_h), "on_release": self.copy_address_action}, + { "text": "Delete", "viewclass": "OneLineListItem", "height": dp(dmi_h), "on_release": self.delete_conversation_action } ] + + ConversationEntry.voice_dropdown = MDDropdownMenu(caller=None, items=dmv_items, position="auto", width=dp(256), elevation=0, radius=dp(3)) + ConversationEntry.voice_dropdown.effect_cls = ScrollEffect + ConversationEntry.voice_dropdown.md_bg_color = self.app.color_hover + + if ConversationEntry.conversation_dropdown == None: + dm_items = [ { "viewclass": "OneLineListItem", "text": "Edit", "height": dp(dmi_h), "on_release": self.edit_action }, + { "viewclass": "OneLineListItem", "text": "Call", "height": dp(dmi_h), "on_release": self.call_action }, + { "text": "Copy Address", "viewclass": "OneLineListItem", "height": dp(dmi_h), "on_release": self.copy_address_action }, + { "text": "Clear Messages", "viewclass": "OneLineListItem", "height": dp(dmi_h), "on_release": self.clear_messages_action }, + { "text": "Clear Telemetry", "viewclass": "OneLineListItem", "height": dp(dmi_h), "on_release": self.clear_telemetry_action }, + { "text": "Delete Conversation", "viewclass": "OneLineListItem", "height": dp(dmi_h), "on_release": self.delete_conversation_action } ] + + ConversationEntry.conversation_dropdown = MDDropdownMenu(caller=None, items=dm_items, position="auto", width=dp(256), elevation=0, radius=dp(3)) + ConversationEntry.conversation_dropdown.effect_cls = ScrollEffect + ConversationEntry.conversation_dropdown.md_bg_color = self.app.color_hover + + def dispatch_update(self): + self.owner_screen.update() + + def dropdown_dismiss(self): + self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() + + def copy_address_action(self): + context_dest = ConversationEntry.conversation_dropdown.context_dest; caller = ConversationEntry.conversation_dropdown.caller + Clipboard.copy(RNS.hexrep(context_dest, delimit=False)) + self.dropdown_dismiss() + + def edit_action(self): + context_dest = ConversationEntry.conversation_dropdown.context_dest; caller = ConversationEntry.conversation_dropdown.caller + RNS.log(f"Menu action from {caller} for {RNS.prettyhexrep(context_dest)}") + dest = context_dest + try: + t_s = time.time() + cd = self.app.sideband._db_conversation(dest) + disp_name = self.app.sideband.raw_display_name(dest) + is_trusted = self.app.sideband.is_trusted(dest, conv_data=cd) + is_object = self.app.sideband.is_object(dest, conv_data=cd) + ptt_enabled = self.app.sideband.ptt_enabled(dest, conv_data=cd) + send_telemetry = self.app.sideband.should_send_telemetry(dest, conv_data=cd) + allow_requests = self.app.sideband.requests_allowed_from(dest, conv_data=cd) + yes_button = MDRectangleFlatButton(text="Save",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_accept, text_color=self.app.color_accept) + no_button = MDRectangleFlatButton(text="Cancel",font_size=dp(18)) + dialog_content = ConvSettings(disp_name=disp_name, context_dest=RNS.hexrep(dest, delimit=False), trusted=is_trusted, + telemetry=send_telemetry, allow_requests=allow_requests, is_object=is_object, ptt_enabled=ptt_enabled) + dialog_content.ids.name_field.font_name = self.app.input_font + dialog = MDDialog(title="Edit Conversation", text="With "+RNS.prettyhexrep(dest), + type="custom", content_cls=dialog_content, buttons=[yes_button, no_button]) + dialog.d_content = dialog_content + def dl_no(s): dialog.dismiss() + def dl_yes(s): + try: + name = dialog.d_content.ids["name_field"].text + trusted = dialog.d_content.ids["trusted_switch"].active + telemetry = dialog.d_content.ids["telemetry_switch"].active + allow_requests = dialog.d_content.ids["allow_requests_switch"].active + conv_is_object = dialog.d_content.ids["is_object_switch"].active + ptt_is_enabled = dialog.d_content.ids["ptt_enabled_switch"].active + if trusted: self.app.sideband.trusted_conversation(dest) + else: self.app.sideband.untrusted_conversation(dest) + if telemetry: self.app.sideband.send_telemetry_in_conversation(dest) + else: self.app.sideband.no_telemetry_in_conversation(dest) + if allow_requests: self.app.sideband.allow_requests_from(dest) + else: self.app.sideband.disallow_requests_from(dest) + if conv_is_object: self.app.sideband.conversation_set_object(dest, True) + else: self.app.sideband.conversation_set_object(dest, False) + if ptt_is_enabled:self.app.sideband.conversation_set_ptt_enabled(dest, True) + else: self.app.sideband.conversation_set_ptt_enabled(dest, False) + self.app.sideband.named_conversation(name, dest) + + except Exception as e: RNS.log("Error while saving conversation settings: "+str(e), RNS.LOG_ERROR) + + dialog.dismiss() + + def cb(dt): self.dispatch_update() + Clock.schedule_once(cb, 0.2) + + yes_button.bind(on_release=dl_yes) + no_button.bind(on_release=dl_no) + self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() + dialog.open() + RNS.log("Generated edit dialog in "+str(RNS.prettytime(time.time()-t_s)), RNS.LOG_DEBUG) + + except Exception as e: + RNS.log("Error while creating conversation settings: "+str(e), RNS.LOG_ERROR) + + def delete_conversation_action(self): + context_dest = ConversationEntry.conversation_dropdown.context_dest; caller = ConversationEntry.conversation_dropdown.caller + if ConversationEntry.delete_dialog == None: + yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject) + no_button = MDRectangleFlatButton(text="No",font_size=dp(18)) + ConversationEntry.delete_dialog = MDDialog( title="Delete conversation?", buttons=[ yes_button, no_button ]) + def dl_yes(s): + ConversationEntry.delete_dialog.dismiss() + self.app.sideband.delete_conversation(ConversationEntry.conversation_dropdown.context_dest) + def cb(dt): self.dispatch_update() + Clock.schedule_once(cb, 0.1) + def dl_no(s): + self.delete_dialog.dismiss() + + yes_button.bind(on_release=dl_yes) + no_button.bind(on_release=dl_no) + + self.dropdown_dismiss() + ConversationEntry.delete_dialog.open() + + def call_action(self): + context_dest = ConversationEntry.conversation_dropdown.context_dest; caller = ConversationEntry.conversation_dropdown.caller + identity = RNS.Identity.recall(ConversationEntry.conversation_dropdown.context_dest) + if identity: self.app.dial_action(identity.hash) + else: toast("Can't call, identity unknown") + self.dropdown_dismiss() + + def clear_messages_action(self): + context_dest = ConversationEntry.conversation_dropdown.context_dest; caller = ConversationEntry.conversation_dropdown.caller + if ConversationEntry.clear_dialog == None: + yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject) + no_button = MDRectangleFlatButton(text="No",font_size=dp(18)) + ConversationEntry.clear_dialog = MDDialog(title="Clear all messages in conversation?", buttons=[ yes_button, no_button ]) + + def dl_yes(s): + ConversationEntry.clear_dialog.dismiss() + self.app.sideband.clear_conversation(ConversationEntry.conversation_dropdown.context_dest) + def dl_no(s): + self.clear_dialog.dismiss() + + yes_button.bind(on_release=dl_yes) + no_button.bind(on_release=dl_no) + + ConversationEntry.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() + self.clear_dialog.open() + RNS.log(f"Menu action from {caller} for {RNS.prettyhexrep(context_dest)}") + + def clear_telemetry_action(self): + context_dest = ConversationEntry.conversation_dropdown.context_dest; caller = ConversationEntry.conversation_dropdown.caller + if ConversationEntry.clear_telemetry_dialog == None: + yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject) + no_button = MDRectangleFlatButton(text="No",font_size=dp(18)) + ConversationEntry.clear_telemetry_dialog = MDDialog( title="Clear all telemetry related to this peer?", buttons=[ yes_button, no_button ]) + def dl_yes(s): + ConversationEntry.clear_telemetry_dialog.dismiss() + self.app.sideband.clear_telemetry(self.conversation_dropdown.context_dest) + def dl_no(s): + ConversationEntry.clear_telemetry_dialog.dismiss() + + yes_button.bind(on_release=dl_yes) + no_button.bind(on_release=dl_no) + + ConversationEntry.voice_dropdown.dismiss(); ConversationEntry.conversation_dropdown.dismiss() + ConversationEntry.clear_telemetry_dialog.open() + + def left_icon_action(self, sender): + RNS.log(f"Action from: {sender} {self.text}") + if self.object: self.app.object_details_action(sender=self, source_dest=self.sb_uid, from_objects=True) + else: self.app.conversation_action(self) + + def right_icon_action(self, sender): + ConversationEntry.voice_dropdown.context_dest = self.sb_uid + ConversationEntry.voice_dropdown.caller = self.ids.right_icon + ConversationEntry.conversation_dropdown.caller = self.ids.right_icon + ConversationEntry.conversation_dropdown.context_dest = self.sb_uid + if self.conv_type == self.app.sideband.CONV_VOICE: ConversationEntry.voice_dropdown.open() + else: ConversationEntry.conversation_dropdown.open() + +class ConversationList(MDRecycleView): + def __init__(self): + super().__init__() + self.data = [] + class Conversations(): def __init__(self, app): self.app = app self.context_dests = [] self.added_item_dests = [] - self.list = None + # self.list = None + self.conversation_list = None self.ids = None if not self.app.root.ids.screen_manager.has_screen("conversations_screen"): @@ -176,362 +383,416 @@ class Conversations(): return iconl - def update_widget(self): + def get_icon_colors(self, conv): + context_dest = conv["dest"] + trusted = conv["trust"] == 1 + appearance_from_all = self.app.sideband.config["display_style_from_all"] + appearance = self.app.sideband.peer_appearance(context_dest, conv=conv) + da = self.app.sideband.DEFAULT_APPEARANCE + ic_s = 24; ic_p = 14 + + fg = None; bg = None; ti_color = None + + if (trusted or appearance_from_all) and self.app.sideband.config["display_style_in_contact_list"] and appearance != None and appearance != da: + fg = appearance[1] or da[1]; bg = appearance[2] or da[2] + ti_color = "Custom" + else: ti_color = None + + return {"ti_color": ti_color, "fg": fg, "bg": bg} + + def update_conversation_list(self): us = time.time() - RNS.log("Updating conversation list widgets", RNS.LOG_DEBUG) - if self.list == None: - self.list = MDList() - - remove_widgets = [] - for w in self.list.children: - if not w.sb_uid in [e["dest"] for e in self.context_dests]: - remove_widgets.append(w) - self.added_item_dests.remove(w.sb_uid) - - for w in remove_widgets: - self.list.remove_widget(w) - - + ConversationEntry.app = self.app + ConversationEntry.owner_screen = self + entries = [] for conv in self.context_dests: context_dest = conv["dest"] conv_type = conv["type"] unread = conv["unread"] last_activity = conv["last_activity"] + colors = self.get_icon_colors(conv) peer_disp_name = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(context_dest))).encode("utf-8")).decode("utf-8") - if not context_dest in self.added_item_dests: - existing_conv = self.app.sideband._db_conversation(context_dest) - is_object = self.app.sideband.is_object(context_dest, conv_data=existing_conv) - ptt_enabled = self.app.sideband.ptt_enabled(context_dest, conv_data=existing_conv) - iconl = self.get_icon(conv) - item = OneLineAvatarIconListItem(text=peer_disp_name, on_release=self.app.conversation_action) - item.add_widget(iconl) - item.last_activity = last_activity - item.iconl = iconl - item.sb_uid = context_dest - item.sb_unread = unread - iconl.sb_uid = context_dest - item.conv_type = conv_type + existing_conv = self.app.sideband._db_conversation(context_dest) + is_object = self.app.sideband.is_object(context_dest, conv_data=existing_conv) + is_trusted = self.app.sideband.is_trusted(context_dest, conv_data=existing_conv) + ptt_enabled = self.app.sideband.ptt_enabled(context_dest, conv_data=existing_conv) + icon = self.trust_icon(conv) - def gen_edit(item): - def x(): - t_s = time.time() - dest = self.conversation_dropdown.context_dest - try: - cd = self.app.sideband._db_conversation(dest) - disp_name = self.app.sideband.raw_display_name(dest) - is_trusted = self.app.sideband.is_trusted(dest, conv_data=cd) - is_object = self.app.sideband.is_object(dest, conv_data=cd) - ptt_enabled = self.app.sideband.ptt_enabled(dest, conv_data=cd) - send_telemetry = self.app.sideband.should_send_telemetry(dest, conv_data=cd) - allow_requests = self.app.sideband.requests_allowed_from(dest, conv_data=cd) + cl_entry = {"icon": icon, "text": peer_disp_name, "conv_type": conv_type, + "ti_color": "Primary", "icon_fg": "#FFFFFF", "icon_bg": "#00000000", + "conv_type": conv_type, "sb_uid": context_dest, "sb_unread": unread, + "trusted": is_trusted, "object": is_object, "last_activity": last_activity} - yes_button = MDRectangleFlatButton(text="Save",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_accept, text_color=self.app.color_accept) - no_button = MDRectangleFlatButton(text="Cancel",font_size=dp(18)) - dialog_content = ConvSettings(disp_name=disp_name, context_dest=RNS.hexrep(dest, delimit=False), trusted=is_trusted, - telemetry=send_telemetry, allow_requests=allow_requests, is_object=is_object, ptt_enabled=ptt_enabled) - dialog_content.ids.name_field.font_name = self.app.input_font + if colors["ti_color"]: cl_entry["ti_color"] = colors["ti_color"] + if colors["fg"]: cl_entry["icon_fg"] = colors["fg"] + if colors["bg"]: cl_entry["icon_bg"] = colors["bg"] + entries.append(cl_entry) - dialog = MDDialog( - title="Edit Conversation", - text= "With "+RNS.prettyhexrep(dest), - type="custom", - content_cls=dialog_content, - buttons=[ yes_button, no_button ], - # elevation=0, - ) - dialog.d_content = dialog_content - def dl_yes(s): - try: - name = dialog.d_content.ids["name_field"].text - trusted = dialog.d_content.ids["trusted_switch"].active - telemetry = dialog.d_content.ids["telemetry_switch"].active - allow_requests = dialog.d_content.ids["allow_requests_switch"].active - conv_is_object = dialog.d_content.ids["is_object_switch"].active - ptt_is_enabled = dialog.d_content.ids["ptt_enabled_switch"].active - if trusted: - self.app.sideband.trusted_conversation(dest) - else: - self.app.sideband.untrusted_conversation(dest) + entries.sort(key=lambda w: (w["trusted"], w["last_activity"]), reverse=True) + self.conversation_list.data = entries - if telemetry: - self.app.sideband.send_telemetry_in_conversation(dest) - else: - self.app.sideband.no_telemetry_in_conversation(dest) + RNS.log("Updated recycle list widgets in "+RNS.prettytime(time.time()-us), RNS.LOG_DEBUG) - if allow_requests: - self.app.sideband.allow_requests_from(dest) - else: - self.app.sideband.disallow_requests_from(dest) + def update_widget(self): + RNS.log("Updating conversation list widgets", RNS.LOG_DEBUG) + # if self.list == None: self.list = MDList() + if self.conversation_list == None: + self.conversation_list = ConversationList() + self.ids.conversation_list_container.add_widget(self.conversation_list) - if conv_is_object: - self.app.sideband.conversation_set_object(dest, True) - else: - self.app.sideband.conversation_set_object(dest, False) + # remove_widgets = [] + # for w in self.list.children: + # if not w.sb_uid in [e["dest"] for e in self.context_dests]: + # remove_widgets.append(w) + # self.added_item_dests.remove(w.sb_uid) - if ptt_is_enabled: - RNS.log("Setting PTT enabled") - self.app.sideband.conversation_set_ptt_enabled(dest, True) - else: - RNS.log("Setting PTT disabled") - self.app.sideband.conversation_set_ptt_enabled(dest, False) + # for w in remove_widgets: + # self.list.remove_widget(w) + + self.update_conversation_list() - self.app.sideband.named_conversation(name, dest) + # us = time.time() + # for conv in self.context_dests: + # context_dest = conv["dest"] + # conv_type = conv["type"] + # unread = conv["unread"] + # last_activity = conv["last_activity"] - except Exception as e: - RNS.log("Error while saving conversation settings: "+str(e), RNS.LOG_ERROR) + # peer_disp_name = multilingual_markup(escape_markup(str(self.app.sideband.peer_display_name(context_dest))).encode("utf-8")).decode("utf-8") + # if not context_dest in self.added_item_dests: + # existing_conv = self.app.sideband._db_conversation(context_dest) + # is_object = self.app.sideband.is_object(context_dest, conv_data=existing_conv) + # ptt_enabled = self.app.sideband.ptt_enabled(context_dest, conv_data=existing_conv) + # iconl = self.get_icon(conv) + # item = OneLineAvatarIconListItem(text=peer_disp_name, on_release=self.app.conversation_action) + # item.add_widget(iconl) + # item.last_activity = last_activity + # item.iconl = iconl + # item.sb_uid = context_dest + # item.sb_unread = unread + # iconl.sb_uid = context_dest + # item.conv_type = conv_type - dialog.dismiss() + # def gen_edit(item): + # def x(): + # t_s = time.time() + # dest = self.conversation_dropdown.context_dest + # try: + # cd = self.app.sideband._db_conversation(dest) + # disp_name = self.app.sideband.raw_display_name(dest) + # is_trusted = self.app.sideband.is_trusted(dest, conv_data=cd) + # is_object = self.app.sideband.is_object(dest, conv_data=cd) + # ptt_enabled = self.app.sideband.ptt_enabled(dest, conv_data=cd) + # send_telemetry = self.app.sideband.should_send_telemetry(dest, conv_data=cd) + # allow_requests = self.app.sideband.requests_allowed_from(dest, conv_data=cd) - def cb(dt): - self.update() - Clock.schedule_once(cb, 0.2) + # yes_button = MDRectangleFlatButton(text="Save",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_accept, text_color=self.app.color_accept) + # no_button = MDRectangleFlatButton(text="Cancel",font_size=dp(18)) + # dialog_content = ConvSettings(disp_name=disp_name, context_dest=RNS.hexrep(dest, delimit=False), trusted=is_trusted, + # telemetry=send_telemetry, allow_requests=allow_requests, is_object=is_object, ptt_enabled=ptt_enabled) + # dialog_content.ids.name_field.font_name = self.app.input_font - def dl_no(s): - dialog.dismiss() + # dialog = MDDialog( + # title="Edit Conversation", + # text= "With "+RNS.prettyhexrep(dest), + # type="custom", + # content_cls=dialog_content, + # buttons=[ yes_button, no_button ], + # # elevation=0, + # ) + # dialog.d_content = dialog_content + # def dl_yes(s): + # try: + # name = dialog.d_content.ids["name_field"].text + # trusted = dialog.d_content.ids["trusted_switch"].active + # telemetry = dialog.d_content.ids["telemetry_switch"].active + # allow_requests = dialog.d_content.ids["allow_requests_switch"].active + # conv_is_object = dialog.d_content.ids["is_object_switch"].active + # ptt_is_enabled = dialog.d_content.ids["ptt_enabled_switch"].active + # if trusted: + # self.app.sideband.trusted_conversation(dest) + # else: + # self.app.sideband.untrusted_conversation(dest) - yes_button.bind(on_release=dl_yes) - no_button.bind(on_release=dl_no) - self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() - dialog.open() - RNS.log("Generated edit dialog in "+str(RNS.prettytime(time.time()-t_s)), RNS.LOG_DEBUG) + # if telemetry: + # self.app.sideband.send_telemetry_in_conversation(dest) + # else: + # self.app.sideband.no_telemetry_in_conversation(dest) - except Exception as e: - RNS.log("Error while creating conversation settings: "+str(e), RNS.LOG_ERROR) + # if allow_requests: + # self.app.sideband.allow_requests_from(dest) + # else: + # self.app.sideband.disallow_requests_from(dest) - return x + # if conv_is_object: + # self.app.sideband.conversation_set_object(dest, True) + # else: + # self.app.sideband.conversation_set_object(dest, False) - def gen_clear(item): - def x(): - if self.clear_dialog == None: - yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject) - no_button = MDRectangleFlatButton(text="No",font_size=dp(18)) + # if ptt_is_enabled: + # RNS.log("Setting PTT enabled") + # self.app.sideband.conversation_set_ptt_enabled(dest, True) + # else: + # RNS.log("Setting PTT disabled") + # self.app.sideband.conversation_set_ptt_enabled(dest, False) - self.clear_dialog = MDDialog( - title="Clear all messages in conversation?", - buttons=[ yes_button, no_button ], - # elevation=0, - ) - def dl_yes(s): - self.clear_dialog.dismiss() - self.app.sideband.clear_conversation(self.conversation_dropdown.context_dest) - def dl_no(s): - self.clear_dialog.dismiss() + # self.app.sideband.named_conversation(name, dest) - yes_button.bind(on_release=dl_yes) - no_button.bind(on_release=dl_no) + # except Exception as e: + # RNS.log("Error while saving conversation settings: "+str(e), RNS.LOG_ERROR) - self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() - self.clear_dialog.open() - return x + # dialog.dismiss() - def gen_clear_telemetry(item): - def x(): - if self.clear_telemetry_dialog == None: - yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject) - no_button = MDRectangleFlatButton(text="No",font_size=dp(18)) + # def cb(dt): + # self.update() + # Clock.schedule_once(cb, 0.2) - self.clear_telemetry_dialog = MDDialog( - title="Clear all telemetry related to this peer?", - buttons=[ yes_button, no_button ], - # elevation=0, - ) - def dl_yes(s): - self.clear_telemetry_dialog.dismiss() - self.app.sideband.clear_telemetry(self.conversation_dropdown.context_dest) - def dl_no(s): - self.clear_telemetry_dialog.dismiss() + # def dl_no(s): + # dialog.dismiss() - yes_button.bind(on_release=dl_yes) - no_button.bind(on_release=dl_no) + # yes_button.bind(on_release=dl_yes) + # no_button.bind(on_release=dl_no) + # self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() + # dialog.open() + # RNS.log("Generated edit dialog in "+str(RNS.prettytime(time.time()-t_s)), RNS.LOG_DEBUG) - self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() - self.clear_telemetry_dialog.open() - return x + # except Exception as e: + # RNS.log("Error while creating conversation settings: "+str(e), RNS.LOG_ERROR) - def gen_del(item): - def x(): - if self.delete_dialog == None: - yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject) - no_button = MDRectangleFlatButton(text="No",font_size=dp(18)) - self.delete_dialog = MDDialog( - title="Delete conversation?", - buttons=[ yes_button, no_button ], - # elevation=0, - ) - def dl_yes(s): - self.delete_dialog.dismiss() - self.app.sideband.delete_conversation(self.conversation_dropdown.context_dest) - def cb(dt): - self.update() - Clock.schedule_once(cb, 0.2) - def dl_no(s): - self.delete_dialog.dismiss() + # return x - yes_button.bind(on_release=dl_yes) - no_button.bind(on_release=dl_no) + # def gen_clear(item): + # def x(): + # if self.clear_dialog == None: + # yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject) + # no_button = MDRectangleFlatButton(text="No",font_size=dp(18)) - self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() - self.delete_dialog.open() - return x + # self.clear_dialog = MDDialog( + # title="Clear all messages in conversation?", + # buttons=[ yes_button, no_button ], + # # elevation=0, + # ) + # def dl_yes(s): + # self.clear_dialog.dismiss() + # self.app.sideband.clear_conversation(self.conversation_dropdown.context_dest) + # def dl_no(s): + # self.clear_dialog.dismiss() - def gen_copy_addr(item): - def x(): - Clipboard.copy(RNS.hexrep(self.conversation_dropdown.context_dest, delimit=False)) - self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() - return x + # yes_button.bind(on_release=dl_yes) + # no_button.bind(on_release=dl_no) - def gen_call(item): - def x(): - identity = RNS.Identity.recall(self.conversation_dropdown.context_dest) - if identity: self.app.dial_action(identity.hash) - else: toast("Can't call, identity unknown") - self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() - return x + # self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() + # self.clear_dialog.open() + # return x - item.iconr = IconRightWidget(icon="dots-vertical"); + # def gen_clear_telemetry(item): + # def x(): + # if self.clear_telemetry_dialog == None: + # yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject) + # no_button = MDRectangleFlatButton(text="No",font_size=dp(18)) - if self.voice_dropdown == None: - dmi_h = 40 - dmv_items = [ - { - "viewclass": "OneLineListItem", - "text": "Edit", - "height": dp(dmi_h), - "on_release": gen_edit(item) - }, - { - "text": "Copy Identity Hash", - "viewclass": "OneLineListItem", - "height": dp(dmi_h), - "on_release": gen_copy_addr(item) - }, - { - "text": "Delete", - "viewclass": "OneLineListItem", - "height": dp(dmi_h), - "on_release": gen_del(item) - } - ] + # self.clear_telemetry_dialog = MDDialog( + # title="Clear all telemetry related to this peer?", + # buttons=[ yes_button, no_button ], + # # elevation=0, + # ) + # def dl_yes(s): + # self.clear_telemetry_dialog.dismiss() + # self.app.sideband.clear_telemetry(self.conversation_dropdown.context_dest) + # def dl_no(s): + # self.clear_telemetry_dialog.dismiss() - self.voice_dropdown = MDDropdownMenu( - caller=item.iconr, - items=dmv_items, - position="auto", - width=dp(256), - elevation=0, - radius=dp(3), - ) - self.voice_dropdown.effect_cls = ScrollEffect - self.voice_dropdown.md_bg_color = self.app.color_hover + # yes_button.bind(on_release=dl_yes) + # no_button.bind(on_release=dl_no) - if self.conversation_dropdown == None: - dmi_h = 40 - dm_items = [ - { - "viewclass": "OneLineListItem", - "text": "Edit", - "height": dp(dmi_h), - "on_release": gen_edit(item) - }, - { - "viewclass": "OneLineListItem", - "text": "Call", - "height": dp(dmi_h), - "on_release": gen_call(item) - }, - { - "text": "Copy Address", - "viewclass": "OneLineListItem", - "height": dp(dmi_h), - "on_release": gen_copy_addr(item) - }, - { - "text": "Clear Messages", - "viewclass": "OneLineListItem", - "height": dp(dmi_h), - "on_release": gen_clear(item) - }, - { - "text": "Clear Telemetry", - "viewclass": "OneLineListItem", - "height": dp(dmi_h), - "on_release": gen_clear_telemetry(item) - }, - { - "text": "Delete Conversation", - "viewclass": "OneLineListItem", - "height": dp(dmi_h), - "on_release": gen_del(item) - } - ] + # self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() + # self.clear_telemetry_dialog.open() + # return x - self.conversation_dropdown = MDDropdownMenu( - caller=item.iconr, - items=dm_items, - position="auto", - width=dp(256), - elevation=0, - radius=dp(3), - ) - self.conversation_dropdown.effect_cls = ScrollEffect - self.conversation_dropdown.md_bg_color = self.app.color_hover + # def gen_del(item): + # def x(): + # if self.delete_dialog == None: + # yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject) + # no_button = MDRectangleFlatButton(text="No",font_size=dp(18)) + # self.delete_dialog = MDDialog( + # title="Delete conversation?", + # buttons=[ yes_button, no_button ], + # # elevation=0, + # ) + # def dl_yes(s): + # self.delete_dialog.dismiss() + # self.app.sideband.delete_conversation(self.conversation_dropdown.context_dest) + # def cb(dt): + # self.update() + # Clock.schedule_once(cb, 0.2) + # def dl_no(s): + # self.delete_dialog.dismiss() - if conv_type == self.app.sideband.CONV_VOICE: - item.dmenu = self.voice_dropdown - else: - item.dmenu = self.conversation_dropdown + # yes_button.bind(on_release=dl_yes) + # no_button.bind(on_release=dl_no) - def callback_factory(ref, dest): - def x(sender): - self.conversation_dropdown.context_dest = dest - self.voice_dropdown.context_dest = dest - ref.dmenu.caller = ref.iconr - ref.dmenu.open() - return x + # self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() + # self.delete_dialog.open() + # return x - item.iconr.bind(on_release=callback_factory(item, context_dest)) + # def gen_copy_addr(item): + # def x(): + # Clipboard.copy(RNS.hexrep(self.conversation_dropdown.context_dest, delimit=False)) + # self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() + # return x - item.add_widget(item.iconr) + # def gen_call(item): + # def x(): + # identity = RNS.Identity.recall(self.conversation_dropdown.context_dest) + # if identity: self.app.dial_action(identity.hash) + # else: toast("Can't call, identity unknown") + # self.voice_dropdown.dismiss(); self.conversation_dropdown.dismiss() + # return x - item.trusted = self.app.sideband.is_trusted(context_dest, conv_data=existing_conv) - item.conv_type = conv_type + # item.iconr = IconRightWidget(icon="dots-vertical"); + + # if self.voice_dropdown == None: + # dmi_h = 40 + # dmv_items = [ + # { + # "viewclass": "OneLineListItem", + # "text": "Edit", + # "height": dp(dmi_h), + # "on_release": gen_edit(item) + # }, + # { + # "text": "Copy Identity Hash", + # "viewclass": "OneLineListItem", + # "height": dp(dmi_h), + # "on_release": gen_copy_addr(item) + # }, + # { + # "text": "Delete", + # "viewclass": "OneLineListItem", + # "height": dp(dmi_h), + # "on_release": gen_del(item) + # } + # ] + + # self.voice_dropdown = MDDropdownMenu( + # caller=item.iconr, + # items=dmv_items, + # position="auto", + # width=dp(256), + # elevation=0, + # radius=dp(3), + # ) + # self.voice_dropdown.effect_cls = ScrollEffect + # self.voice_dropdown.md_bg_color = self.app.color_hover + + # if self.conversation_dropdown == None: + # dmi_h = 40 + # dm_items = [ + # { + # "viewclass": "OneLineListItem", + # "text": "Edit", + # "height": dp(dmi_h), + # "on_release": gen_edit(item) + # }, + # { + # "viewclass": "OneLineListItem", + # "text": "Call", + # "height": dp(dmi_h), + # "on_release": gen_call(item) + # }, + # { + # "text": "Copy Address", + # "viewclass": "OneLineListItem", + # "height": dp(dmi_h), + # "on_release": gen_copy_addr(item) + # }, + # { + # "text": "Clear Messages", + # "viewclass": "OneLineListItem", + # "height": dp(dmi_h), + # "on_release": gen_clear(item) + # }, + # { + # "text": "Clear Telemetry", + # "viewclass": "OneLineListItem", + # "height": dp(dmi_h), + # "on_release": gen_clear_telemetry(item) + # }, + # { + # "text": "Delete Conversation", + # "viewclass": "OneLineListItem", + # "height": dp(dmi_h), + # "on_release": gen_del(item) + # } + # ] + + # self.conversation_dropdown = MDDropdownMenu( + # caller=item.iconr, + # items=dm_items, + # position="auto", + # width=dp(256), + # elevation=0, + # radius=dp(3), + # ) + # self.conversation_dropdown.effect_cls = ScrollEffect + # self.conversation_dropdown.md_bg_color = self.app.color_hover + + # if conv_type == self.app.sideband.CONV_VOICE: + # item.dmenu = self.voice_dropdown + # else: + # item.dmenu = self.conversation_dropdown + + # def callback_factory(ref, dest): + # def x(sender): + # self.conversation_dropdown.context_dest = dest + # self.voice_dropdown.context_dest = dest + # ref.dmenu.caller = ref.iconr + # ref.dmenu.open() + # return x + + # item.iconr.bind(on_release=callback_factory(item, context_dest)) + + # item.add_widget(item.iconr) + + # item.trusted = self.app.sideband.is_trusted(context_dest, conv_data=existing_conv) + # item.conv_type = conv_type - self.added_item_dests.append(context_dest) - self.list.add_widget(item) + # self.added_item_dests.append(context_dest) + # self.list.add_widget(item) - else: - for w in self.list.children: - if w.sb_uid == context_dest: - trust_icon = self.trust_icon(conv) - trusted = conv["trust"] == 1 - da = self.app.sideband.DEFAULT_APPEARANCE - appearance = self.app.sideband.peer_appearance(context_dest, conv) - if trusted and self.app.sideband.config["display_style_in_contact_list"] and appearance != None and appearance != da: - fg = appearance[1] or da[1]; bg = appearance[2] or da[2] - ti_color = "Custom" - else: - ti_color = None + # else: + # for w in self.list.children: + # if w.sb_uid == context_dest: + # trust_icon = self.trust_icon(conv) + # trusted = conv["trust"] == 1 + # da = self.app.sideband.DEFAULT_APPEARANCE + # appearance = self.app.sideband.peer_appearance(context_dest, conv) + # if trusted and self.app.sideband.config["display_style_in_contact_list"] and appearance != None and appearance != da: + # fg = appearance[1] or da[1]; bg = appearance[2] or da[2] + # ti_color = "Custom" + # else: + # ti_color = None - w.last_activity = last_activity - if ti_color != None: - w.iconl.theme_icon_color = ti_color - if bg != None: w.iconl.md_bg_color = bg - if fg != None: w.iconl.icon_color = fg - else: - w.iconl.theme_icon_color = "Primary" - w.iconl.md_bg_color = [0,0,0,0] + # w.last_activity = last_activity + # if ti_color != None: + # w.iconl.theme_icon_color = ti_color + # if bg != None: w.iconl.md_bg_color = bg + # if fg != None: w.iconl.icon_color = fg + # else: + # w.iconl.theme_icon_color = "Primary" + # w.iconl.md_bg_color = [0,0,0,0] - if w.iconl.icon != trust_icon: w.iconl.icon = trust_icon - if w.sb_unread != unread: w.sb_unread = unread - if w.text != peer_disp_name: w.text = peer_disp_name + # if w.iconl.icon != trust_icon: w.iconl.icon = trust_icon + # if w.sb_unread != unread: w.sb_unread = unread + # if w.text != peer_disp_name: w.text = peer_disp_name - self.list.children.sort(key=lambda w: (w.trusted, w.last_activity)) + # self.list.children.sort(key=lambda w: (w.trusted, w.last_activity)) - RNS.log("Updated conversation list widgets in "+RNS.prettytime(time.time()-us), RNS.LOG_DEBUG) + # RNS.log("Updated conversation list widgets in "+RNS.prettytime(time.time()-us), RNS.LOG_DEBUG) def get_widget(self): - return self.list + return self.conversation_list conv_screen_kv = """ MDScreen: @@ -558,11 +819,41 @@ MDScreen: ['account-plus', lambda x: root.app.new_conversation_action(self)], ] - ScrollView: - id: conversations_scrollview + # ScrollView: + # id: conversations_scrollview + + MDBoxLayout: + orientation: "vertical" + id: conversation_list_container """ Builder.load_string(""" + + + IconLeftWidget: + id: left_icon + theme_icon_color: root.ti_color + icon_color: root.icon_fg + md_bg_color: root.icon_bg + icon: root.icon + _default_icon_pad: dp(14) + icon_size: dp(24) + + IconRightWidget: + id: right_icon + icon: "dots-vertical" + +: + viewclass: "ConversationEntry" + effect_cls: "ScrollEffect" + + RecycleBoxLayout: + default_size: None, dp(57) + default_size_hint: 1, None + size_hint_y: None + height: self.minimum_height + orientation: "vertical" + orientation: "vertical" spacing: "24dp"