Sideband/sbapp/ui/conversations.py

640 lines
25 KiB
Python
Raw Normal View History

2022-04-07 15:03:53 -04:00
import RNS
import time
2022-04-07 15:03:53 -04:00
from kivy.metrics import dp,sp
2022-04-07 15:03:53 -04:00
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, BooleanProperty
from kivymd.uix.list import MDList, IconLeftWidget, IconRightWidget, OneLineAvatarIconListItem
from kivymd.uix.menu import MDDropdownMenu
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.core.clipboard import Clipboard
2022-10-18 13:17:55 -04:00
from kivy.effects.scroll import ScrollEffect
2022-04-07 15:03:53 -04:00
from kivymd.uix.button import MDRectangleFlatButton
2022-04-07 15:03:53 -04:00
from kivymd.uix.dialog import MDDialog
2023-10-22 08:01:31 -04:00
from kivy.lang.builder import Builder
2024-01-05 12:05:34 -05:00
from kivy.utils import escape_markup
if RNS.vendor.platformutils.get_platform() == "android":
from ui.helpers import multilingual_markup
else:
from .helpers import multilingual_markup
2022-04-07 15:03:53 -04:00
class NewConv(BoxLayout):
pass
class MsgSync(BoxLayout):
pass
class ConvSettings(BoxLayout):
disp_name = StringProperty()
context_dest = StringProperty()
2022-04-07 15:03:53 -04:00
trusted = BooleanProperty()
telemetry = BooleanProperty()
allow_requests = BooleanProperty()
is_object = BooleanProperty()
2022-04-07 15:03:53 -04:00
class Conversations():
def __init__(self, app):
self.app = app
self.context_dests = []
self.added_item_dests = []
self.list = None
2023-10-22 08:01:31 -04:00
self.ids = None
2023-10-22 08:01:31 -04:00
if not self.app.root.ids.screen_manager.has_screen("conversations_screen"):
self.screen = Builder.load_string(conv_screen_kv)
self.screen.app = self.app
2023-10-22 08:01:31 -04:00
self.ids = self.screen.ids
self.app.root.ids.screen_manager.add_widget(self.screen)
self.conversation_dropdown = None
self.delete_dialog = None
self.clear_dialog = None
2023-10-20 19:02:43 -04:00
self.clear_telemetry_dialog = None
2022-04-07 15:03:53 -04:00
self.update()
def reload(self):
self.clear_list()
self.update()
def clear_list(self):
if self.list != None:
self.list.clear_widgets()
self.context_dests = []
self.added_item_dests = []
def update(self):
# if self.app.sideband.getstate("app.flags.unread_conversations"):
# self.clear_list()
2022-04-07 15:03:53 -04:00
self.context_dests = self.app.sideband.list_conversations(conversations=self.app.include_conversations, objects=self.app.include_objects)
view_title = "Conversations"
if self.app.include_conversations:
if self.app.include_objects:
view_title = "Conversations & Objects"
elif self.app.include_objects:
view_title = "Objects"
self.screen.ids.conversations_bar.title = view_title
2022-04-07 15:03:53 -04:00
self.update_widget()
self.app.sideband.setstate("app.flags.unread_conversations", False)
self.app.sideband.setstate("app.flags.new_conversations", False)
2022-10-12 10:33:05 -04:00
self.app.sideband.setstate("wants.viewupdate.conversations", False)
2022-04-07 15:03:53 -04:00
def trust_icon(self, conv):
context_dest = conv["dest"]
unread = conv["unread"]
appearance = self.app.sideband.peer_appearance(context_dest, conv=conv)
is_trusted = conv["trust"] == 1
appearance_from_all = self.app.sideband.config["display_style_from_all"]
trust_icon = "account-question"
da = self.app.sideband.DEFAULT_APPEARANCE
if (is_trusted or appearance_from_all) and self.app.sideband.config["display_style_in_contact_list"] and appearance != None and appearance != da:
if unread:
trust_icon = "email"
else:
trust_icon = appearance[0] or da[0];
else:
if self.app.sideband.requests_allowed_from(context_dest):
2023-10-29 15:59:27 -04:00
if unread:
if is_trusted:
trust_icon = "email-seal"
else:
trust_icon = "email"
2023-10-29 15:59:27 -04:00
else:
trust_icon = "account-lock-open"
else:
if is_trusted:
if unread:
trust_icon = "email-seal"
else:
trust_icon = "account-check"
2023-10-29 15:59:27 -04:00
else:
if unread:
trust_icon = "email"
else:
trust_icon = "account-question"
return trust_icon
def get_icon(self, conv):
context_dest = conv["dest"]
unread = conv["unread"]
last_activity = conv["last_activity"]
trusted = conv["trust"] == 1
appearance = self.app.sideband.peer_appearance(context_dest, conv=conv)
is_object = self.app.sideband.is_object(context_dest, conv_data=conv)
da = self.app.sideband.DEFAULT_APPEARANCE
ic_s = 24; ic_p = 14
conv_icon = self.trust_icon(conv)
fg = None; bg = None; ti_color = None
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
if is_object:
def gen_rel_func():
def x(ws):
self.app.object_details_action(sender=ws, from_objects=True)
return x
rel_func = gen_rel_func()
else:
rel_func = self.app.conversation_action
iconl = IconLeftWidget(
icon=conv_icon, theme_icon_color=ti_color,
icon_color=fg, md_bg_color=bg,
on_release=rel_func)
iconl.source_dest = context_dest
iconl._default_icon_pad = dp(ic_p)
iconl.icon_size = dp(ic_s)
return iconl
2022-04-07 15:03:53 -04:00
def update_widget(self):
us = time.time()
RNS.log("Updating conversation list widgets", RNS.LOG_DEBUG)
2022-04-07 15:03:53 -04:00
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)
2022-04-07 15:03:53 -04:00
for conv in self.context_dests:
context_dest = conv["dest"]
unread = conv["unread"]
2023-09-20 15:41:26 -04:00
last_activity = conv["last_activity"]
2022-04-07 15:03:53 -04:00
2024-01-05 12:05:34 -05:00
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)
iconl = self.get_icon(conv)
2024-01-05 12:05:34 -05:00
item = OneLineAvatarIconListItem(text=peer_disp_name, on_release=self.app.conversation_action)
2022-04-07 15:03:53 -04:00
item.add_widget(iconl)
2023-09-20 15:41:26 -04:00
item.last_activity = last_activity
item.iconl = iconl
2022-04-07 15:03:53 -04:00
item.sb_uid = context_dest
item.sb_unread = unread
2022-10-02 06:45:06 -04:00
iconl.sb_uid = context_dest
2022-04-07 15:03:53 -04:00
def gen_edit(item):
2022-04-07 15:03:53 -04:00
def x():
t_s = time.time()
dest = self.conversation_dropdown.context_dest
2022-04-07 15:03:53 -04:00
try:
cd = self.app.sideband._db_conversation(dest)
2023-09-20 19:59:06 -04:00
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)
send_telemetry = self.app.sideband.should_send_telemetry(dest, conv_data=cd)
allow_requests = self.app.sideband.requests_allowed_from(dest, conv_data=cd)
2022-04-07 15:03:53 -04:00
2022-10-13 16:12:39 -04:00
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)
dialog_content.ids.name_field.font_name = self.app.input_font
2024-01-05 12:05:34 -05:00
2022-04-07 15:03:53 -04:00
dialog = MDDialog(
title="Edit Conversation",
text= "With "+RNS.prettyhexrep(dest),
2022-04-07 15:03:53 -04:00
type="custom",
content_cls=dialog_content,
buttons=[ yes_button, no_button ],
2022-10-02 18:56:39 -04:00
# elevation=0,
2022-04-07 15:03:53 -04:00
)
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
2022-04-07 15:03:53 -04:00
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)
2022-04-07 15:03:53 -04:00
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.update()
Clock.schedule_once(cb, 0.2)
2022-04-07 15:03:53 -04:00
def dl_no(s):
dialog.dismiss()
yes_button.bind(on_release=dl_yes)
no_button.bind(on_release=dl_no)
item.dmenu.dismiss()
dialog.open()
RNS.log("Generated edit dialog in "+str(RNS.prettytime(time.time()-t_s)), RNS.LOG_DEBUG)
2022-04-07 15:03:53 -04:00
except Exception as e:
RNS.log("Error while creating conversation settings: "+str(e), RNS.LOG_ERROR)
return x
def gen_clear(item):
2022-04-07 15:03:53 -04:00
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.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()
yes_button.bind(on_release=dl_yes)
no_button.bind(on_release=dl_no)
2022-04-07 15:03:53 -04:00
item.dmenu.dismiss()
self.clear_dialog.open()
2022-04-07 15:03:53 -04:00
return x
2023-10-20 19:02:43 -04:00
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))
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()
yes_button.bind(on_release=dl_yes)
no_button.bind(on_release=dl_no)
item.dmenu.dismiss()
self.clear_telemetry_dialog.open()
return x
def gen_del(item):
2022-04-07 15:03:53 -04:00
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()
yes_button.bind(on_release=dl_yes)
no_button.bind(on_release=dl_no)
2022-04-07 15:03:53 -04:00
item.dmenu.dismiss()
self.delete_dialog.open()
2022-04-07 15:03:53 -04:00
return x
# def gen_move_to(item):
# def x():
# item.dmenu.dismiss()
# self.app.sideband.conversation_set_object(self.conversation_dropdown.context_dest, not self.app.sideband.is_object(self.conversation_dropdown.context_dest))
# self.app.conversations_view.update()
# return x
def gen_copy_addr(item):
def x():
Clipboard.copy(RNS.hexrep(self.conversation_dropdown.context_dest, delimit=False))
item.dmenu.dismiss()
return x
2023-07-09 20:49:58 -04:00
item.iconr = IconRightWidget(icon="dots-vertical");
if self.conversation_dropdown == None:
obj_str = "conversations" if is_object else "objects"
dmi_h = 40
dm_items = [
{
"viewclass": "OneLineListItem",
"text": "Edit",
"height": dp(dmi_h),
"on_release": gen_edit(item)
},
{
"text": "Copy Address",
"viewclass": "OneLineListItem",
"height": dp(dmi_h),
"on_release": gen_copy_addr(item)
},
# {
# "text": "Move to objects",
# "viewclass": "OneLineListItem",
# "height": dp(dmi_h),
# "on_release": gen_move_to(item)
# },
{
"text": "Clear Messages",
"viewclass": "OneLineListItem",
"height": dp(dmi_h),
"on_release": gen_clear(item)
},
2023-10-20 19:02:43 -04:00
{
"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(
2023-07-09 20:49:58 -04:00
caller=item.iconr,
items=dm_items,
position="auto",
2023-07-10 13:20:24 -04:00
width=dp(256),
elevation=0,
radius=dp(3),
)
2022-10-18 13:17:55 -04:00
self.conversation_dropdown.effect_cls = ScrollEffect
2024-03-10 20:21:54 -04:00
self.conversation_dropdown.md_bg_color = self.app.color_hover
2022-04-07 15:03:53 -04:00
item.dmenu = self.conversation_dropdown
def callback_factory(ref, dest):
2022-04-07 15:03:53 -04:00
def x(sender):
self.conversation_dropdown.context_dest = dest
ref.dmenu.caller = ref.iconr
2022-04-07 15:03:53 -04:00
ref.dmenu.open()
return x
item.iconr.bind(on_release=callback_factory(item, context_dest))
2022-04-07 15:03:53 -04:00
item.add_widget(item.iconr)
2023-10-19 21:02:14 -04:00
item.trusted = self.app.sideband.is_trusted(context_dest, conv_data=existing_conv)
2022-04-07 15:03:53 -04:00
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
2023-09-20 15:41:26 -04:00
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
2024-01-05 12:05:34 -05:00
if w.text != peer_disp_name: w.text = peer_disp_name
2023-10-19 21:02:14 -04:00
self.list.children.sort(key=lambda w: (w.trusted, w.last_activity))
2023-09-20 15:41:26 -04:00
RNS.log("Updated conversation list widgets in "+RNS.prettytime(time.time()-us), RNS.LOG_DEBUG)
2022-04-07 15:03:53 -04:00
def get_widget(self):
2023-10-22 08:01:31 -04:00
return self.list
conv_screen_kv = """
MDScreen:
name: "conversations_screen"
BoxLayout:
orientation: "vertical"
MDTopAppBar:
title: "Conversations"
id: conversations_bar
2023-10-22 08:01:31 -04:00
anchor_title: "left"
elevation: 0
left_action_items:
[
['menu', lambda x: root.app.nav_drawer.set_state("open")],
2023-10-22 08:01:31 -04:00
]
right_action_items:
[
2023-10-22 11:14:32 -04:00
['access-point', lambda x: root.app.announce_now_action(self)],
['webhook', lambda x: root.app.connectivity_status(self)],
['qrcode', lambda x: root.app.ingest_lxm_action(self)],
['email-sync', lambda x: root.app.lxmf_sync_action(self)],
['account-plus', lambda x: root.app.new_conversation_action(self)],
2023-10-22 08:01:31 -04:00
]
ScrollView:
id: conversations_scrollview
"""
Builder.load_string("""
<NewConv>
orientation: "vertical"
spacing: "24dp"
size_hint_y: None
height: dp(250)
MDTextField:
id: n_address_field
max_text_length: 32
hint_text: "Address"
helper_text: "Error, check your input"
helper_text_mode: "on_error"
text: ""
font_size: dp(24)
MDTextField:
id: n_name_field
hint_text: "Name"
text: ""
font_size: dp(24)
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
padding: [0,0,dp(8),dp(24)]
height: dp(48)
MDLabel:
id: "trusted_switch_label"
text: "Trusted"
font_style: "H6"
MDSwitch:
id: n_trusted
pos_hint: {"center_y": 0.3}
active: False
<ConvSettings>
orientation: "vertical"
spacing: "16dp"
size_hint_y: None
padding: [0, 0, 0, dp(8)]
height: self.minimum_height
MDTextField:
id: dest_field
hint_text: "Address"
text: root.context_dest
# disabled: True
font_size: dp(18)
MDTextField:
id: name_field
hint_text: "Name"
text: root.disp_name
font_size: dp(18)
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
padding: [0,0,dp(8),0]
height: dp(32)
2023-10-22 08:01:31 -04:00
MDLabel:
id: trusted_switch_label
text: "Trusted"
font_style: "H6"
MDSwitch:
id: trusted_switch
pos_hint: {"center_y": 0.43}
active: root.trusted
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
padding: [0,0,dp(8),0]
height: dp(32)
2023-10-22 08:01:31 -04:00
MDLabel:
id: telemetry_switch_label
text: "Send Telemetry"
2023-10-22 08:01:31 -04:00
font_style: "H6"
MDSwitch:
id: telemetry_switch
pos_hint: {"center_y": 0.43}
active: root.telemetry
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
padding: [0,0,dp(8),0]
height: dp(32)
MDLabel:
id: allow_requests_label
text: "Allow Requests"
font_style: "H6"
MDSwitch:
id: allow_requests_switch
pos_hint: {"center_y": 0.43}
active: root.allow_requests
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
padding: [0,0,dp(8),0]
height: dp(32)
MDLabel:
id: is_object_label
text: "Is Object"
font_style: "H6"
MDSwitch:
id: is_object_switch
pos_hint: {"center_y": 0.43}
active: root.is_object
2023-10-22 08:01:31 -04:00
<MsgSync>
orientation: "vertical"
spacing: "24dp"
size_hint_y: None
padding: [0, 0, 0, dp(16)]
height: self.minimum_height+dp(24)
MDProgressBar:
id: sync_progress
2023-10-27 14:28:33 -04:00
type: "determinate"
2023-10-22 08:01:31 -04:00
value: 0
MDLabel:
id: sync_status
hint_text: "Name"
text: "Initiating sync..."
""")