Sideband/sbapp/ui/messages.py

577 lines
26 KiB
Python
Raw Normal View History

2022-04-07 13:03:53 -06:00
import time
import RNS
import LXMF
from kivy.metrics import dp,sp
2022-04-07 13:03:53 -06:00
from kivy.core.clipboard import Clipboard
from kivymd.uix.card import MDCard
from kivymd.uix.menu import MDDropdownMenu
2022-10-08 10:01:33 -06:00
# from kivymd.uix.behaviors import RoundedRectangularElevationBehavior, FakeRectangularElevationBehavior
from kivymd.uix.behaviors import CommonElevationBehavior
2022-04-07 13:03:53 -06:00
from kivy.properties import StringProperty, BooleanProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
2022-04-07 13:03:53 -06:00
2022-11-23 05:28:26 -07:00
from kivymd.uix.button import MDRectangleFlatButton, MDRectangleFlatIconButton
2022-04-07 13:03:53 -06:00
from kivymd.uix.dialog import MDDialog
2022-11-22 06:25:56 -07:00
import os
import plyer
import subprocess
import shlex
2022-07-07 14:16:10 -06:00
if RNS.vendor.platformutils.get_platform() == "android":
2023-10-20 15:38:28 -06:00
from sideband.sense import Telemeter
2022-11-22 06:25:56 -07:00
from ui.helpers import ts_format, file_ts_format, mdc
from ui.helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light
2022-07-07 14:16:10 -06:00
else:
2023-10-20 15:38:28 -06:00
from sbapp.sideband.sense import Telemeter
2022-11-22 06:25:56 -07:00
from .helpers import ts_format, file_ts_format, mdc
from .helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light
2022-04-07 13:03:53 -06:00
2022-10-08 10:01:33 -06:00
class ListLXMessageCard(MDCard):
# class ListLXMessageCard(MDCard, FakeRectangularElevationBehavior):
2022-04-07 13:03:53 -06:00
text = StringProperty()
heading = StringProperty()
class Messages():
def __init__(self, app, context_dest):
self.app = app
self.context_dest = context_dest
self.new_messages = []
2022-04-07 13:03:53 -06:00
self.added_item_hashes = []
self.added_messages = 0
2022-04-07 13:03:53 -06:00
self.latest_message_timestamp = None
self.earliest_message_timestamp = time.time()
self.loading_earlier_messages = False
2022-04-07 13:03:53 -06:00
self.list = None
self.widgets = []
self.send_error_dialog = None
2022-11-23 05:28:26 -07:00
self.load_more_button = None
2022-04-07 13:03:53 -06:00
self.update()
def reload(self):
if self.list != None:
self.list.clear_widgets()
self.new_messages = []
2022-04-07 13:03:53 -06:00
self.added_item_hashes = []
self.added_messages = 0
2022-04-07 13:03:53 -06:00
self.latest_message_timestamp = None
self.widgets = []
self.update()
def load_more(self, dt):
for new_message in self.app.sideband.list_messages(self.context_dest, before=self.earliest_message_timestamp,limit=5):
self.new_messages.append(new_message)
if len(self.new_messages) > 0:
self.loading_earlier_messages = True
self.list.remove_widget(self.load_more_button)
2022-11-23 05:28:26 -07:00
def update(self, limit=8):
for new_message in self.app.sideband.list_messages(self.context_dest, after=self.latest_message_timestamp,limit=limit):
self.new_messages.append(new_message)
2022-11-23 05:28:26 -07:00
self.db_message_count = self.app.sideband.count_messages(self.context_dest)
if self.load_more_button == None:
self.load_more_button = MDRectangleFlatIconButton(
icon="message-text-clock-outline",
text="Load earlier messages",
font_size=dp(18),
theme_text_color="Custom",
size_hint=[1.0, None],
)
def lmcb(sender):
Clock.schedule_once(self.load_more, 0.15)
self.load_more_button.bind(on_release=lmcb)
2022-11-23 05:28:26 -07:00
2022-04-07 13:03:53 -06:00
if self.list == None:
2022-10-02 09:17:55 -06:00
layout = GridLayout(cols=1, spacing=dp(16), padding=dp(16), size_hint_y=None)
2022-04-07 13:03:53 -06:00
layout.bind(minimum_height=layout.setter('height'))
self.list = layout
2022-11-23 05:28:26 -07:00
c_ts = time.time()
if len(self.new_messages) > 0:
2022-04-07 13:03:53 -06:00
self.update_widget()
if (len(self.added_item_hashes) < self.db_message_count) and not self.load_more_button in self.list.children:
self.list.add_widget(self.load_more_button, len(self.list.children))
2022-04-07 13:03:53 -06:00
2022-10-02 06:51:01 -06:00
if self.app.sideband.config["dark_ui"]:
intensity_msgs = intensity_msgs_dark
else:
intensity_msgs = intensity_msgs_light
2022-04-07 13:03:53 -06:00
for w in self.widgets:
m = w.m
2022-10-08 10:01:33 -06:00
if self.app.sideband.config["dark_ui"]:
w.line_color = (1.0, 1.0, 1.0, 0.25)
else:
w.line_color = (1.0, 1.0, 1.0, 0.5)
2022-04-07 13:03:53 -06:00
if m["state"] == LXMF.LXMessage.SENDING or m["state"] == LXMF.LXMessage.OUTBOUND:
msg = self.app.sideband.message(m["hash"])
if msg["state"] == LXMF.LXMessage.DELIVERED:
w.md_bg_color = msg_color = mdc(color_delivered, intensity_msgs)
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
titlestr = ""
if msg["title"]:
titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n"
w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Delivered"
2022-04-07 13:03:53 -06:00
m["state"] = msg["state"]
2022-11-22 06:25:56 -07:00
if msg["method"] == LXMF.LXMessage.PAPER:
w.md_bg_color = msg_color = mdc(color_paper, intensity_msgs)
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
titlestr = ""
if msg["title"]:
titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n"
w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Paper Message"
m["state"] = msg["state"]
2022-04-07 13:03:53 -06:00
if msg["method"] == LXMF.LXMessage.PROPAGATED and msg["state"] == LXMF.LXMessage.SENT:
w.md_bg_color = msg_color = mdc(color_propagated, intensity_msgs)
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
titlestr = ""
if msg["title"]:
titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n"
w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] On Propagation Net"
2022-04-07 13:03:53 -06:00
m["state"] = msg["state"]
if msg["state"] == LXMF.LXMessage.FAILED:
w.md_bg_color = msg_color = mdc(color_failed, intensity_msgs)
txstr = time.strftime(ts_format, time.localtime(msg["sent"]))
titlestr = ""
if msg["title"]:
titlestr = "[b]Title[/b] "+msg["title"].decode("utf-8")+"\n"
w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Failed"
2022-04-07 13:03:53 -06:00
m["state"] = msg["state"]
2023-10-16 14:21:38 -06:00
w.dmenu.items.append(w.dmenu.retry_item)
2022-04-07 13:03:53 -06:00
def update_widget(self):
2022-10-02 06:51:01 -06:00
if self.app.sideband.config["dark_ui"]:
intensity_msgs = intensity_msgs_dark
2022-10-02 17:36:21 -06:00
mt_color = [1.0, 1.0, 1.0, 0.8]
2022-10-02 06:51:01 -06:00
else:
intensity_msgs = intensity_msgs_light
2022-10-02 17:36:21 -06:00
mt_color = [1.0, 1.0, 1.0, 0.95]
2022-10-02 06:51:01 -06:00
if self.loading_earlier_messages:
self.new_messages.reverse()
for m in self.new_messages:
2022-04-07 13:03:53 -06:00
if not m["hash"] in self.added_item_hashes:
txstr = time.strftime(ts_format, time.localtime(m["sent"]))
rxstr = time.strftime(ts_format, time.localtime(m["received"]))
titlestr = ""
2023-10-21 17:12:13 -06:00
extra_telemetry = {}
phy_stats_str = ""
if "extras" in m and m["extras"] != None:
phy_stats = m["extras"]
if "q" in phy_stats:
try:
lq = round(float(phy_stats["q"]), 1)
phy_stats_str += "[b]Link Quality[/b] "+str(lq)+"% "
extra_telemetry["quality"] = lq
except:
pass
if "rssi" in phy_stats:
try:
lr = round(float(phy_stats["rssi"]), 1)
phy_stats_str += "[b]RSSI[/b] "+str(lr)+"dBm "
extra_telemetry["rssi"] = lr
except:
pass
if "snr" in phy_stats:
try:
ls = round(float(phy_stats["snr"]), 1)
phy_stats_str += "[b]SNR[/b] "+str(ls)+"dB "
extra_telemetry["snr"] = ls
except:
pass
2022-04-07 13:03:53 -06:00
if m["title"]:
titlestr = "[b]Title[/b] "+m["title"].decode("utf-8")+"\n"
if m["source"] == self.app.sideband.lxmf_destination.hash:
if m["state"] == LXMF.LXMessage.DELIVERED:
msg_color = mdc(color_delivered, intensity_msgs)
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Delivered"
2022-04-07 13:03:53 -06:00
elif m["method"] == LXMF.LXMessage.PROPAGATED and m["state"] == LXMF.LXMessage.SENT:
msg_color = mdc(color_propagated, intensity_msgs)
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] On Propagation Net"
2022-04-07 13:03:53 -06:00
2022-11-22 06:25:56 -07:00
elif m["method"] == LXMF.LXMessage.PAPER:
msg_color = mdc(color_paper, intensity_msgs)
heading_str = titlestr+"[b]Created[/b] "+txstr+"\n[b]State[/b] Paper Message"
2022-04-07 13:03:53 -06:00
elif m["state"] == LXMF.LXMessage.FAILED:
msg_color = mdc(color_failed, intensity_msgs)
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Failed"
2022-04-07 13:03:53 -06:00
elif m["state"] == LXMF.LXMessage.OUTBOUND or m["state"] == LXMF.LXMessage.SENDING:
msg_color = mdc(color_unknown, intensity_msgs)
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Sending "
2022-04-07 13:03:53 -06:00
else:
msg_color = mdc(color_unknown, intensity_msgs)
heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Unknown"
2022-04-07 13:03:53 -06:00
else:
2022-10-02 14:28:09 -06:00
msg_color = mdc(color_received, intensity_msgs)
2023-10-21 17:12:13 -06:00
heading_str = titlestr
if phy_stats_str != "" and self.app.sideband.config["advanced_stats"]:
heading_str += phy_stats_str+"\n"
heading_str += "[b]Received[/b] "+rxstr+"\n[b]Sent[/b] "+txstr
2022-04-07 13:03:53 -06:00
item = ListLXMessageCard(
text=m["content"].decode("utf-8"),
heading=heading_str,
md_bg_color=msg_color,
)
item.sb_uid = m["hash"]
item.m = m
2022-10-02 17:36:21 -06:00
item.ids.heading_text.theme_text_color = "Custom"
item.ids.heading_text.text_color = mt_color
item.ids.content_text.theme_text_color = "Custom"
item.ids.content_text.text_color = mt_color
item.ids.msg_submenu.theme_text_color = "Custom"
item.ids.msg_submenu.text_color = mt_color
2022-04-07 13:03:53 -06:00
def gen_del(mhash, item):
def x():
2022-10-13 14:12:39 -06:00
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))
2022-04-07 13:03:53 -06:00
dialog = MDDialog(
title="Delete message?",
2022-04-07 13:03:53 -06:00
buttons=[ yes_button, no_button ],
2022-10-02 16:56:39 -06:00
# elevation=0,
2022-04-07 13:03:53 -06:00
)
def dl_yes(s):
dialog.dismiss()
self.app.sideband.delete_message(mhash)
def cb(dt):
self.reload()
Clock.schedule_once(cb, 0.2)
2022-04-07 13:03:53 -06: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()
return x
2023-10-16 14:21:38 -06:00
def gen_retry(mhash, mcontent, item):
def x():
self.app.root.ids.message_text.text = mcontent.decode("utf-8")
self.app.sideband.delete_message(mhash)
self.app.message_send_action()
item.dmenu.dismiss()
def cb(dt):
self.reload()
Clock.schedule_once(cb, 0.2)
return x
2022-04-07 13:03:53 -06:00
def gen_copy(msg, item):
def x():
Clipboard.copy(msg)
item.dmenu.dismiss()
return x
2023-10-21 17:12:13 -06:00
def gen_copy_telemetry(packed_telemetry, extra_telemetry, item):
2023-10-20 15:38:28 -06:00
def x():
try:
telemeter = Telemeter.from_packed(packed_telemetry)
2023-10-21 17:12:13 -06:00
tlm = telemeter.read_all()
if extra_telemetry and len(extra_telemetry) != 0:
tlm["physical_link"] = extra_telemetry
Clipboard.copy(str(tlm))
2023-10-20 15:38:28 -06:00
item.dmenu.dismiss()
except Exception as e:
RNS.log("An error occurred while decoding telemetry. The contained exception was: "+str(e), RNS.LOG_ERROR)
Clipboard.copy("Could not decode telemetry")
return x
2022-11-22 06:25:56 -07:00
def gen_copy_lxm_uri(lxm, item):
def x():
Clipboard.copy(lxm.as_uri())
item.dmenu.dismiss()
return x
def gen_save_qr(lxm, item):
if RNS.vendor.platformutils.is_android():
def x():
2022-11-22 11:47:13 -07:00
qr_image = lxm.as_qr()
hash_str = RNS.hexrep(lxm.hash[-2:], delimit=False)
filename = "Paper_Message_"+time.strftime(file_ts_format, time.localtime(m["sent"]))+"_"+hash_str+".png"
# filename = "Paper_Message.png"
self.app.share_image(qr_image, filename)
2022-11-22 06:25:56 -07:00
item.dmenu.dismiss()
return x
else:
def x():
try:
qr_image = lxm.as_qr()
hash_str = RNS.hexrep(lxm.hash[-2:], delimit=False)
filename = "Paper_Message_"+time.strftime(file_ts_format, time.localtime(m["sent"]))+"_"+hash_str+".png"
if RNS.vendor.platformutils.is_darwin():
save_path = str(plyer.storagepath.get_downloads_dir()+filename).replace("file://", "")
else:
save_path = plyer.storagepath.get_downloads_dir()+"/"+filename
2022-11-22 06:25:56 -07:00
qr_image.save(save_path)
item.dmenu.dismiss()
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(
title="QR Code Saved",
text="The paper message has been saved to: "+save_path+"",
buttons=[ ok_button ],
# elevation=0,
)
def dl_ok(s):
dialog.dismiss()
ok_button.bind(on_release=dl_ok)
dialog.open()
except Exception as e:
item.dmenu.dismiss()
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(
title="Error",
text="Could not save the paper message QR-code to:\n\n"+save_path+"\n\n"+str(e),
buttons=[ ok_button ],
# elevation=0,
)
def dl_ok(s):
dialog.dismiss()
ok_button.bind(on_release=dl_ok)
dialog.open()
return x
def gen_print_qr(lxm, item):
if RNS.vendor.platformutils.is_android():
def x():
item.dmenu.dismiss()
return x
else:
def x():
try:
qr_image = lxm.as_qr()
qr_tmp_path = self.app.sideband.tmp_dir+"/"+str(RNS.hexrep(lxm.hash, delimit=False))
qr_image.save(qr_tmp_path)
print_command = self.app.sideband.config["print_command"]+" "+qr_tmp_path
return_code = subprocess.call(shlex.split(print_command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
os.unlink(qr_tmp_path)
item.dmenu.dismiss()
except Exception as e:
item.dmenu.dismiss()
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(
title="Error",
text="Could not print the paper message QR-code.\n\n"+str(e),
buttons=[ ok_button ],
# elevation=0,
)
def dl_ok(s):
dialog.dismiss()
ok_button.bind(on_release=dl_ok)
dialog.open()
return x
2023-10-16 14:21:38 -06:00
retry_item = {
"viewclass": "OneLineListItem",
"text": "Retry",
"height": dp(40),
"on_release": gen_retry(m["hash"], m["content"], item)
}
2022-11-22 06:25:56 -07:00
if m["method"] == LXMF.LXMessage.PAPER:
if RNS.vendor.platformutils.is_android():
qr_save_text = "Share QR Code"
2022-11-22 11:47:13 -07:00
dm_items = [
{
"viewclass": "OneLineListItem",
"text": "Share QR Code",
"height": dp(40),
"on_release": gen_save_qr(m["lxm"], item)
},
{
"viewclass": "OneLineListItem",
"text": "Copy LXM URI",
"height": dp(40),
"on_release": gen_copy_lxm_uri(m["lxm"], item)
},
{
"viewclass": "OneLineListItem",
"text": "Copy message text",
"height": dp(40),
"on_release": gen_copy(m["content"].decode("utf-8"), item)
},
{
"text": "Delete",
"viewclass": "OneLineListItem",
"height": dp(40),
"on_release": gen_del(m["hash"], item)
}
]
2022-11-22 06:25:56 -07:00
else:
2022-11-22 11:47:13 -07:00
dm_items = [
{
"viewclass": "OneLineListItem",
"text": "Print QR Code",
"height": dp(40),
"on_release": gen_print_qr(m["lxm"], item)
},
{
"viewclass": "OneLineListItem",
"text": "Save QR Code",
"height": dp(40),
"on_release": gen_save_qr(m["lxm"], item)
},
{
"viewclass": "OneLineListItem",
"text": "Copy LXM URI",
"height": dp(40),
"on_release": gen_copy_lxm_uri(m["lxm"], item)
},
{
"viewclass": "OneLineListItem",
"text": "Copy message text",
"height": dp(40),
"on_release": gen_copy(m["content"].decode("utf-8"), item)
},
{
"text": "Delete",
"viewclass": "OneLineListItem",
"height": dp(40),
"on_release": gen_del(m["hash"], item)
}
]
2022-11-22 06:25:56 -07:00
else:
2023-10-16 14:21:38 -06:00
if m["state"] == LXMF.LXMessage.FAILED:
dm_items = [
retry_item,
{
"viewclass": "OneLineListItem",
"text": "Copy",
"height": dp(40),
"on_release": gen_copy(m["content"].decode("utf-8"), item)
},
{
"text": "Delete",
"viewclass": "OneLineListItem",
"height": dp(40),
"on_release": gen_del(m["hash"], item)
}
]
else:
2023-10-20 15:38:28 -06:00
if "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_TELEMETRY in m["lxm"].fields:
packed_telemetry = m["lxm"].fields[LXMF.FIELD_TELEMETRY]
dm_items = [
{
"viewclass": "OneLineListItem",
"text": "Copy",
"height": dp(40),
"on_release": gen_copy(m["content"].decode("utf-8"), item)
},
{
"viewclass": "OneLineListItem",
"text": "Copy telemetry",
"height": dp(40),
2023-10-21 17:12:13 -06:00
"on_release": gen_copy_telemetry(packed_telemetry, extra_telemetry, item)
2023-10-20 15:38:28 -06:00
},
{
"text": "Delete",
"viewclass": "OneLineListItem",
"height": dp(40),
"on_release": gen_del(m["hash"], item)
}
]
else:
dm_items = [
{
"viewclass": "OneLineListItem",
"text": "Copy",
"height": dp(40),
"on_release": gen_copy(m["content"].decode("utf-8"), item)
},
{
"text": "Delete",
"viewclass": "OneLineListItem",
"height": dp(40),
"on_release": gen_del(m["hash"], item)
}
]
2022-04-07 13:03:53 -06:00
item.dmenu = MDDropdownMenu(
caller=item.ids.msg_submenu,
items=dm_items,
2023-07-10 11:20:24 -06:00
position="auto",
width=dp(256),
elevation=0,
radius=dp(3),
2022-04-07 13:03:53 -06:00
)
2023-10-16 14:21:38 -06:00
item.dmenu.retry_item = retry_item
2022-04-07 13:03:53 -06:00
def callback_factory(ref):
def x(sender):
ref.dmenu.open()
return x
# Bind menu open
item.ids.msg_submenu.bind(on_release=callback_factory(item))
if self.loading_earlier_messages:
insert_pos = len(self.list.children)
else:
insert_pos = 0
2022-04-07 13:03:53 -06:00
self.added_item_hashes.append(m["hash"])
self.widgets.append(item)
self.list.add_widget(item, insert_pos)
2022-04-07 13:03:53 -06:00
if self.latest_message_timestamp == None or m["received"] > self.latest_message_timestamp:
self.latest_message_timestamp = m["received"]
if self.earliest_message_timestamp == None or m["received"] < self.earliest_message_timestamp:
self.earliest_message_timestamp = m["received"]
self.added_messages += len(self.new_messages)
self.new_messages = []
2022-11-23 05:28:26 -07:00
2022-04-07 13:03:53 -06:00
def get_widget(self):
return self.list
def close_send_error_dialog(self, sender=None):
if self.send_error_dialog:
self.send_error_dialog.dismiss()