diff --git a/sbapp/main.py b/sbapp/main.py index 726818c..7ea2f8a 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -1,6 +1,6 @@ __debug_build__ = False __disable_shaders__ = True -__version__ = "0.3.0" +__version__ = "0.4.0" __variant__ = "beta" import sys @@ -382,6 +382,35 @@ class SidebandApp(MDApp): self.check_bluetooth_permissions() + def on_new_intent(self, intent): + RNS.log("Received intent", RNS.LOG_DEBUG) + intent_action = intent.getAction() + action = None + data = None + + if intent_action == "android.intent.action.WEB_SEARCH": + SearchManager = autoclass('android.app.SearchManager') + data = intent.getStringExtra(SearchManager.QUERY) + + if data.lower().startswith(LXMF.LXMessage.URI_SCHEMA): + action = "lxm_uri" + + if intent_action == "android.intent.action.VIEW": + data = intent.getData().toString() + if data.lower().startswith(LXMF.LXMessage.URI_SCHEMA): + action = "lxm_uri" + + if action != None: + self.handle_action(action, data) + + def handle_action(self, action, data): + if action == "lxm_uri": + self.ingest_lxm_uri(data) + + def ingest_lxm_uri(self, lxm_uri): + RNS.log("Ingesting LXMF paper message from URI: "+str(lxm_uri), RNS.LOG_DEBUG) + self.sideband.lxm_ingest_uri(lxm_uri) + def build(self): FONT_PATH = self.sideband.asset_dir+"/fonts" if RNS.vendor.platformutils.is_darwin(): @@ -396,6 +425,9 @@ class SidebandApp(MDApp): activity = autoclass('org.kivy.android.PythonActivity').mActivity activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) + from android import activity as a_activity + a_activity.bind(on_new_intent=self.on_new_intent) + screen = Builder.load_string(root_layout) return screen @@ -435,6 +467,22 @@ class SidebandApp(MDApp): if self.conversations_view != None: self.conversations_view.update() + if self.sideband.getstate("lxm_uri_ingest.result"): + info_text = self.sideband.getstate("lxm_uri_ingest.result") + self.sideband.setstate("lxm_uri_ingest.result", False) + ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18)) + dialog = MDDialog( + title="Message Scan", + text=info_text, + buttons=[ ok_button ], + # elevation=0, + ) + def dl_ok(s): + dialog.dismiss() + + ok_button.bind(on_release=dl_ok) + dialog.open() + def on_start(self): self.last_exit_event = time.time() self.root.ids.screen_manager.transition.duration = 0.25 @@ -597,6 +645,7 @@ class SidebandApp(MDApp): Clock.schedule_once(cb, 0.15) def open_conversation(self, context_dest): + self.outbound_mode_paper = False if self.sideband.config["propagation_by_default"]: self.outbound_mode_propagation = True else: @@ -662,11 +711,17 @@ class SidebandApp(MDApp): else: msg_content = self.root.ids.message_text.text context_dest = self.root.ids.messages_scrollview.active_conversation - if self.sideband.send_message(msg_content, context_dest, self.outbound_mode_propagation): + if self.outbound_mode_paper: + if self.sideband.paper_message(msg_content, context_dest): + self.root.ids.message_text.text = "" + self.root.ids.messages_scrollview.scroll_y = 0 + self.jobs(0) + + elif self.sideband.send_message(msg_content, context_dest, self.outbound_mode_propagation): self.root.ids.message_text.text = "" - self.root.ids.messages_scrollview.scroll_y = 0 self.jobs(0) + else: self.messages_view.send_error_dialog = MDDialog( title="Error", @@ -688,23 +743,34 @@ class SidebandApp(MDApp): def message_propagation_action(self, sender): - if self.outbound_mode_propagation: + if self.outbound_mode_paper: + self.outbound_mode_paper = False self.outbound_mode_propagation = False else: - self.outbound_mode_propagation = True + if self.outbound_mode_propagation: + self.outbound_mode_paper = True + self.outbound_mode_propagation = False + else: + self.outbound_mode_propagation = True + self.outbound_mode_paper = False + self.update_message_widgets() def update_message_widgets(self): toolbar_items = self.root.ids.messages_toolbar.ids.right_actions.children mode_item = toolbar_items[1] - if not self.outbound_mode_propagation: - mode_item.icon = "lan-connect" - self.root.ids.message_text.hint_text = "Write message for direct delivery" + if self.outbound_mode_paper: + mode_item.icon = "qrcode" + self.root.ids.message_text.hint_text = "Paper message" else: - mode_item.icon = "upload-network" - self.root.ids.message_text.hint_text = "Write message for propagation" - # self.root.ids.message_text.hint_text = "Write message for delivery via propagation nodes" + if not self.outbound_mode_propagation: + mode_item.icon = "lan-connect" + self.root.ids.message_text.hint_text = "Message for direct delivery" + else: + mode_item.icon = "upload-network" + self.root.ids.message_text.hint_text = "Message for propagation" + # self.root.ids.message_text.hint_text = "Write message for delivery via propagation nodes" def key_query_action(self, sender): context_dest = self.root.ids.messages_scrollview.active_conversation @@ -791,7 +857,51 @@ class SidebandApp(MDApp): self.connectivity_updater.cancel() self.connectivity_updater = Clock.schedule_interval(cs_updater, 1.0) + def ingest_lxm_action(self, sender): + def cb(dt): + self.open_ingest_lxm_dialog(sender) + Clock.schedule_once(cb, 0.15) + def open_ingest_lxm_dialog(self, sender=None): + try: + cancel_button = MDRectangleFlatButton(text="Cancel",font_size=dp(18)) + ingest_button = MDRectangleFlatButton(text="Read LXM",font_size=dp(18), theme_text_color="Custom", line_color=self.color_accept, text_color=self.color_accept) + + dialog = MDDialog( + title="Ingest Paper Message", + text="You can read LXMF paper messages into this program by scanning a QR-code containing the message with your device camera or QR-scanner app, and then opening the resulting link in Sideband.\n\nAlternatively, you can copy an [b]lxm://[/b] link from any source to your clipboard, and ingest it using the [i]Read LXM[/i] button below.", + buttons=[ ingest_button, cancel_button ], + ) + def dl_yes(s): + try: + lxm_uri = Clipboard.paste() + if not lxm_uri.lower().startswith(LXMF.LXMessage.URI_SCHEMA+"://"): + lxm_uri = LXMF.LXMessage.URI_SCHEMA+"://"+lxm_uri + + self.ingest_lxm_uri(lxm_uri) + dialog.dismiss() + + except Exception as e: + response = "Error ingesting message from URI: "+str(e) + RNS.log(response, RNS.LOG_ERROR) + self.sideband.setstate("lxm_uri_ingest.result", response) + dialog.dismiss() + + def dl_no(s): + dialog.dismiss() + + def dl_ds(s): + self.dialog_open = False + + ingest_button.bind(on_release=dl_yes) + cancel_button.bind(on_release=dl_no) + + dialog.bind(on_dismiss=dl_ds) + dialog.open() + self.dialog_open = True + + except Exception as e: + RNS.log("Error while creating ingest LXM dialog: "+str(e), RNS.LOG_ERROR) def lxmf_sync_action(self, sender): def cb(dt): @@ -921,7 +1031,7 @@ class SidebandApp(MDApp): self.root.ids.information_scrollview.effect_cls = ScrollEffect self.root.ids.information_logo.icon = self.sideband.asset_dir+"/rns_256.png" - info = "This is Sideband v"+__version__+" "+__variant__+", on RNS v"+RNS.__version__+"\n\nHumbly build using the following open components:\n\n - [b]Reticulum[/b] (MIT License)\n - [b]LXMF[/b] (MIT License)\n - [b]KivyMD[/b] (MIT License)\n - [b]Kivy[/b] (MIT License)\n - [b]Python[/b] (PSF License)"+"\n\nGo to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support the project.\n\nThe Sideband app is Copyright (c) 2022 Mark Qvist / unsigned.io\n\nPermission is granted to freely share and distribute binary copies of Sideband v"+__version__+" "+__variant__+", so long as no payment or compensation is charged for said distribution or sharing.\n\nIf you were charged or paid anything for this copy of Sideband, please report it to [b]license@unsigned.io[/b].\n\nTHIS IS EXPERIMENTAL SOFTWARE - USE AT YOUR OWN RISK AND RESPONSIBILITY" + info = "This is Sideband v"+__version__+" "+__variant__+", on RNS v"+RNS.__version__+" and LXMF v"+LXMF.__version__+".\n\nHumbly build using the following open components:\n\n - [b]Reticulum[/b] (MIT License)\n - [b]LXMF[/b] (MIT License)\n - [b]KivyMD[/b] (MIT License)\n - [b]Kivy[/b] (MIT License)\n - [b]Python[/b] (PSF License)"+"\n\nGo to [u][ref=link]https://unsigned.io/donate[/ref][/u] to support the project.\n\nThe Sideband app is Copyright (c) 2022 Mark Qvist / unsigned.io\n\nPermission is granted to freely share and distribute binary copies of Sideband v"+__version__+" "+__variant__+", so long as no payment or compensation is charged for said distribution or sharing.\n\nIf you were charged or paid anything for this copy of Sideband, please report it to [b]license@unsigned.io[/b].\n\nTHIS IS EXPERIMENTAL SOFTWARE - USE AT YOUR OWN RISK AND RESPONSIBILITY" self.root.ids.information_info.text = info self.root.ids.information_info.bind(on_ref_press=link_exec) self.root.ids.screen_manager.transition.direction = "left" @@ -1003,6 +1113,17 @@ class SidebandApp(MDApp): self.sideband.config["lxmf_sync_limit"] = self.root.ids.settings_lxmf_sync_limit.active self.sideband.save_configuration() + def save_print_command(sender=None, event=None): + in_cmd = self.root.ids.settings_print_command.text + if in_cmd == "": + new_cmd = "lp" + else: + new_cmd = in_cmd + + self.sideband.config["print_command"] = new_cmd.strip() + self.root.ids.settings_print_command.text = self.sideband.config["print_command"] + self.sideband.save_configuration() + def save_lxmf_periodic_sync(sender=None, event=None, save=True): if self.root.ids.settings_lxmf_periodic_sync.active: self.widget_hide(self.root.ids.lxmf_syncslider_container, False) @@ -1029,6 +1150,13 @@ class SidebandApp(MDApp): self.root.ids.settings_display_name.bind(on_text_validate=save_disp_name) self.root.ids.settings_display_name.bind(focus=save_disp_name) + if RNS.vendor.platformutils.is_android(): + self.widget_hide(self.root.ids.settings_print_command, True) + else: + self.root.ids.settings_print_command.text = self.sideband.config["print_command"] + self.root.ids.settings_print_command.bind(on_text_validate=save_print_command) + self.root.ids.settings_print_command.bind(focus=save_print_command) + if self.sideband.config["lxmf_propagation_node"] == None: prop_node_addr = "" else: diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index 2de8361..baf3d68 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -120,6 +120,7 @@ class SidebandCore(): self.identity_path = self.app_dir+"/app_storage/primary_identity" self.db_path = self.app_dir+"/app_storage/sideband.db" self.lxmf_storage = self.app_dir+"/app_storage/" + self.tmp_dir = self.app_dir+"/app_storage/tmp" self.first_run = True @@ -129,6 +130,11 @@ class SidebandCore(): else: self.__load_config() self.first_run = False + + if not os.path.isdir(self.tmp_dir): + os.makedirs(self.tmp_dir) + else: + self.clear_tmp_dir() except Exception as e: RNS.log("Error while configuring Sideband: "+str(e), RNS.LOG_ERROR) @@ -165,6 +171,12 @@ class SidebandCore(): RNS.Transport.register_announce_handler(self) RNS.Transport.register_announce_handler(self.propagation_detector) + def clear_tmp_dir(self): + if os.path.isdir(self.tmp_dir): + for file in os.listdir(self.tmp_dir): + fpath = self.tmp_dir+"/"+file + os.unlink(fpath) + def __init_config(self): RNS.log("Creating new Sideband configuration...") if os.path.isfile(self.identity_path): @@ -189,6 +201,8 @@ class SidebandCore(): self.config["lxmf_sync_interval"] = 43200 self.config["last_lxmf_propagation_node"] = None self.config["nn_home_node"] = None + self.config["print_command"] = "lp" + # Connectivity self.config["connect_transport"] = False self.config["connect_local"] = True @@ -277,6 +291,8 @@ class SidebandCore(): self.config["lxmf_sync_interval"] = 43200 if not "notifications_on" in self.config: self.config["notifications_on"] = True + if not "print_command" in self.config: + self.config["print_command"] = "lp" if not "connect_transport" in self.config: self.config["connect_transport"] = False @@ -945,7 +961,20 @@ class SidebandCore(): return None else: entry = result[0] - lxm = LXMF.LXMessage.unpack_from_bytes(entry[10]) + + lxm_method = entry[7] + if lxm_method == LXMF.LXMessage.PAPER: + lxm_data = msgpack.unpackb(entry[10]) + packed_lxm = lxm_data[0] + paper_packed_lxm = lxm_data[1] + else: + packed_lxm = entry[10] + + lxm = LXMF.LXMessage.unpack_from_bytes(packed_lxm, original_method = lxm_method) + + if lxm.desired_method == LXMF.LXMessage.PAPER: + lxm.paper_packed = paper_packed_lxm + message = { "hash": lxm.hash, "dest": lxm.destination_hash, @@ -980,7 +1009,19 @@ class SidebandCore(): else: messages = [] for entry in result: - lxm = LXMF.LXMessage.unpack_from_bytes(entry[10]) + lxm_method = entry[7] + if lxm_method == LXMF.LXMessage.PAPER: + lxm_data = msgpack.unpackb(entry[10]) + packed_lxm = lxm_data[0] + paper_packed_lxm = lxm_data[1] + else: + packed_lxm = entry[10] + + lxm = LXMF.LXMessage.unpack_from_bytes(packed_lxm, original_method = lxm_method) + + if lxm.desired_method == LXMF.LXMessage.PAPER: + lxm.paper_packed = paper_packed_lxm + message = { "hash": lxm.hash, "dest": lxm.destination_hash, @@ -1006,6 +1047,11 @@ class SidebandCore(): if not lxm.packed: lxm.pack() + if lxm.method == LXMF.LXMessage.PAPER: + packed_lxm = msgpack.packb([lxm.packed, lxm.paper_packed]) + else: + packed_lxm = lxm.packed + query = "INSERT INTO lxm (lxm_hash, dest, source, title, tx_ts, rx_ts, state, method, t_encrypted, t_encryption, data) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" data = ( lxm.hash, @@ -1018,7 +1064,7 @@ class SidebandCore(): lxm.method, lxm.transport_encrypted, lxm.transport_encryption, - lxm.packed + packed_lxm, ) dbc.execute(query, data) @@ -1611,6 +1657,26 @@ class SidebandCore(): else: self.lxm_ingest(message, originator=True) + def paper_message(self, content, destination_hash): + try: + if content == "": + raise ValueError("Message content cannot be empty") + + dest_identity = RNS.Identity.recall(destination_hash) + dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") + source = self.lxmf_destination + + desired_method = LXMF.LXMessage.PAPER + lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method) + + self.lxm_ingest(lxm, originator=True) + + return True + + except Exception as e: + RNS.log("Error while creating paper message: "+str(e), RNS.LOG_ERROR) + return False + def send_message(self, content, destination_hash, propagation): try: if content == "": @@ -1665,6 +1731,30 @@ class SidebandCore(): return True + def lxm_ingest_uri(self, uri): + local_delivery_signal = "local_delivery_occurred" + duplicate_signal = "duplicate_lxm" + ingest_result = self.message_router.ingest_lxm_uri( + uri, + signal_local_delivery=local_delivery_signal, + signal_duplicate=duplicate_signal + ) + + if ingest_result == False: + response = "The URI contained no decodable messages" + + elif ingest_result == local_delivery_signal: + response = "Message was decoded, decrypted successfully, and added to your conversation list." + + elif ingest_result == duplicate_signal: + response = "The decoded message has already been processed by the LXMF Router, and will not be ingested again." + + else: + # TODO: Add message to sneakernet queues + response = "The decoded message was not addressed to your LXMF address, and has been discarded." + + self.setstate("lxm_uri_ingest.result", response) + def lxm_ingest(self, message, originator = False): should_notify = False is_trusted = False diff --git a/sbapp/ui/helpers.py b/sbapp/ui/helpers.py index a645ce2..68f9c72 100644 --- a/sbapp/ui/helpers.py +++ b/sbapp/ui/helpers.py @@ -6,6 +6,7 @@ from kivymd.uix.list import OneLineIconListItem, MDList, IconLeftWidget, IconRig from kivy.properties import StringProperty ts_format = "%Y-%m-%d %H:%M:%S" +file_ts_format = "%Y_%m_%d_%H_%M_%S" def mdc(color, hue=None): if hue == None: @@ -14,6 +15,7 @@ def mdc(color, hue=None): color_received = "LightGreen" color_delivered = "Blue" +color_paper = "Indigo" color_propagated = "Indigo" color_failed = "Red" color_unknown = "Gray" diff --git a/sbapp/ui/layouts.py b/sbapp/ui/layouts.py index f2c5e99..31fe752 100644 --- a/sbapp/ui/layouts.py +++ b/sbapp/ui/layouts.py @@ -88,6 +88,7 @@ MDNavigationLayout: ] right_action_items: [ + ['qrcode', lambda x: root.ids.screen_manager.app.ingest_lxm_action(self)], ['webhook', lambda x: root.ids.screen_manager.app.connectivity_status(self)], ['access-point', lambda x: root.ids.screen_manager.app.announce_now_action(self)], ['email-sync', lambda x: root.ids.screen_manager.app.lxmf_sync_action(self)], @@ -96,7 +97,7 @@ MDNavigationLayout: ScrollView: id: conversations_scrollview - + MDScreen: name: "messages_screen" @@ -1007,6 +1008,14 @@ MDNavigationLayout: max_text_length: 32 font_size: dp(24) + MDTextField: + id: settings_print_command + hint_text: "Print Command" + disabled: False + text: "" + max_text_length: 32 + font_size: dp(24) + MDLabel: text: "" font_style: "H6" diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py index 15521cc..310e756 100644 --- a/sbapp/ui/messages.py +++ b/sbapp/ui/messages.py @@ -16,13 +16,17 @@ from kivy.clock import Clock from kivymd.uix.button import MDRectangleFlatButton from kivymd.uix.dialog import MDDialog +import os +import plyer +import subprocess +import shlex if RNS.vendor.platformutils.get_platform() == "android": - from ui.helpers import ts_format, mdc - from ui.helpers import color_received, color_delivered, color_propagated, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light + 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 else: - from .helpers import ts_format, mdc - from .helpers import color_received, color_delivered, color_propagated, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light + 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 class ListLXMessageCard(MDCard): # class ListLXMessageCard(MDCard, FakeRectangularElevationBehavior): @@ -85,6 +89,15 @@ class Messages(): w.heading = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] Delivered" m["state"] = msg["state"] + 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"] + 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"])) @@ -130,6 +143,10 @@ class Messages(): msg_color = mdc(color_propagated, intensity_msgs) heading_str = titlestr+"[b]Sent[/b] "+txstr+"\n[b]State[/b] On Propagation Net" + 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" + 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" @@ -189,25 +206,153 @@ class Messages(): def gen_copy(msg, item): def x(): Clipboard.copy(msg) - RNS.log(str(item)) item.dmenu.dismiss() return x - 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) - } - ] + 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(): + 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" + save_path = plyer.storagepath.get_downloads_dir()+"/"+filename + 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 + + if m["method"] == LXMF.LXMessage.PAPER: + if RNS.vendor.platformutils.is_android(): + qr_save_text = "Share QR Code" + else: + qr_save_text = "Save QR Code" + + dm_items = [ + { + "viewclass": "OneLineListItem", + "text": "Print QR Code", + "height": dp(40), + "on_release": gen_print_qr(m["lxm"], item) + }, + { + "viewclass": "OneLineListItem", + "text": qr_save_text, + "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) + } + ] + 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) + } + ] item.dmenu = MDDropdownMenu( caller=item.ids.msg_submenu,