Added basic LXST voice call UI

This commit is contained in:
Mark Qvist 2025-03-09 18:32:31 +01:00
parent a0a03c9eba
commit 143f440df7
5 changed files with 298 additions and 79 deletions

View file

@ -1,6 +1,6 @@
__debug_build__ = False __debug_build__ = False
__disable_shaders__ = False __disable_shaders__ = False
__version__ = "1.4.0" __version__ = "1.5.0"
__variant__ = "" __variant__ = ""
import sys import sys
@ -1597,14 +1597,18 @@ class SidebandApp(MDApp):
self.conversation_action(item) self.conversation_action(item)
def conversation_action(self, sender): def conversation_action(self, sender):
if sender.conv_type == self.sideband.CONV_P2P:
context_dest = sender.sb_uid context_dest = sender.sb_uid
def cb(dt): def cb(dt): self.open_conversation(context_dest)
self.open_conversation(context_dest) def cbu(dt): self.conversations_view.update()
def cbu(dt):
self.conversations_view.update()
Clock.schedule_once(cb, 0.15) Clock.schedule_once(cb, 0.15)
Clock.schedule_once(cbu, 0.15+0.25) Clock.schedule_once(cbu, 0.15+0.25)
elif sender.conv_type == self.sideband.CONV_VOICE:
identity_hash = sender.sb_uid
def cb(dt): self.dial_action(identity_hash)
Clock.schedule_once(cb, 0.15)
def open_conversation(self, context_dest, direction="left"): def open_conversation(self, context_dest, direction="left"):
self.rec_dialog_is_open = False self.rec_dialog_is_open = False
self.outbound_mode_paper = False self.outbound_mode_paper = False
@ -2750,7 +2754,8 @@ class SidebandApp(MDApp):
n_address = dialog.d_content.ids["n_address_field"].text n_address = dialog.d_content.ids["n_address_field"].text
n_name = dialog.d_content.ids["n_name_field"].text n_name = dialog.d_content.ids["n_name_field"].text
n_trusted = dialog.d_content.ids["n_trusted"].active n_trusted = dialog.d_content.ids["n_trusted"].active
new_result = self.sideband.new_conversation(n_address, n_name, n_trusted) n_voice_only = dialog.d_content.ids["n_voice_only"].active
new_result = self.sideband.new_conversation(n_address, n_name, n_trusted, n_voice_only)
except Exception as e: except Exception as e:
RNS.log("Error while creating conversation: "+str(e), RNS.LOG_ERROR) RNS.log("Error while creating conversation: "+str(e), RNS.LOG_ERROR)
@ -5257,7 +5262,7 @@ class SidebandApp(MDApp):
self.voice_screen = Voice(self) self.voice_screen = Voice(self)
self.voice_ready = True self.voice_ready = True
def voice_open(self, sender=None, direction="left", no_transition=False): def voice_open(self, sender=None, direction="left", no_transition=False, dial_on_complete=None):
if no_transition: if no_transition:
self.root.ids.screen_manager.transition = self.no_transition self.root.ids.screen_manager.transition = self.no_transition
else: else:
@ -5271,21 +5276,29 @@ class SidebandApp(MDApp):
if no_transition: if no_transition:
self.root.ids.screen_manager.transition = self.slide_transition self.root.ids.screen_manager.transition = self.slide_transition
def voice_action(self, sender=None, direction="left"): self.voice_screen.update_call_status()
if dial_on_complete:
self.voice_screen.dial_target = dial_on_complete
self.voice_screen.screen.ids.identity_hash.text = RNS.hexrep(dial_on_complete, delimit=False)
Clock.schedule_once(self.voice_screen.dial_action, 0.25)
def voice_action(self, sender=None, direction="left", dial_on_complete=None):
if self.voice_ready: if self.voice_ready:
self.voice_open(direction=direction) self.voice_open(direction=direction, dial_on_complete=dial_on_complete)
else: else:
self.loader_action(direction=direction) self.loader_action(direction=direction)
def final(dt): def final(dt):
self.voice_init() self.voice_init()
def o(dt): def o(dt):
self.voice_open(no_transition=True) self.voice_open(no_transition=True, dial_on_complete=dial_on_complete)
Clock.schedule_once(o, ll_ot) Clock.schedule_once(o, ll_ot)
Clock.schedule_once(final, ll_ft) Clock.schedule_once(final, ll_ft)
def close_sub_voice_action(self, sender=None): def close_sub_voice_action(self, sender=None):
self.voice_action(direction="right") self.voice_action(direction="right")
def dial_action(self, identity_hash):
self.voice_action(dial_on_complete=identity_hash)
### Telemetry Screen ### Telemetry Screen
###################################### ######################################

View file

@ -107,6 +107,7 @@ class SidebandCore():
CONV_P2P = 0x01 CONV_P2P = 0x01
CONV_GROUP = 0x02 CONV_GROUP = 0x02
CONV_BROADCAST = 0x03 CONV_BROADCAST = 0x03
CONV_VOICE = 0x04
MAX_ANNOUNCES = 24 MAX_ANNOUNCES = 24
@ -2639,6 +2640,7 @@ class SidebandCore():
"last_rx": last_rx, "last_rx": last_rx,
"last_tx": last_tx, "last_tx": last_tx,
"last_activity": last_activity, "last_activity": last_activity,
"type": entry[4],
"trust": entry[5], "trust": entry[5],
"data": data, "data": data,
} }
@ -2790,6 +2792,27 @@ class SidebandCore():
self.__event_conversations_changed() self.__event_conversations_changed()
def _db_create_voice_object(self, identity_hash, name = None, trust = False):
RNS.log("Creating voice object for "+RNS.prettyhexrep(identity_hash), RNS.LOG_DEBUG)
with self.db_lock:
db = self.__db_connect()
dbc = db.cursor()
def_name = "".encode("utf-8")
query = "INSERT INTO conv (dest_context, last_tx, last_rx, unread, type, trust, name, data) values (?, ?, ?, ?, ?, ?, ?, ?)"
data = (identity_hash, 0, time.time(), 0, SidebandCore.CONV_VOICE, 0, def_name, msgpack.packb(None))
dbc.execute(query, data)
db.commit()
if trust:
self._db_conversation_set_trusted(identity_hash, True)
if name != None and name != "":
self._db_conversation_set_name(identity_hash, name)
self.__event_conversations_changed()
def _db_delete_message(self, msg_hash): def _db_delete_message(self, msg_hash):
RNS.log("Deleting message "+RNS.prettyhexrep(msg_hash)) RNS.log("Deleting message "+RNS.prettyhexrep(msg_hash))
with self.db_lock: with self.db_lock:
@ -4630,7 +4653,7 @@ class SidebandCore():
RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR) RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR)
return False return False
def new_conversation(self, dest_str, name = "", trusted = False): def new_conversation(self, dest_str, name = "", trusted = False, voice_only = False):
if len(dest_str) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8*2: if len(dest_str) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8*2:
return False return False
@ -4640,7 +4663,8 @@ class SidebandCore():
RNS.log("Cannot create conversation with own LXMF address", RNS.LOG_ERROR) RNS.log("Cannot create conversation with own LXMF address", RNS.LOG_ERROR)
return False return False
else: else:
self._db_create_conversation(addr_b, name, trusted) if not voice_only: self._db_create_conversation(addr_b, name, trusted)
else: self._db_create_voice_object(addr_b, name, trusted)
except Exception as e: except Exception as e:
RNS.log("Error while creating conversation: "+str(e), RNS.LOG_ERROR) RNS.log("Error while creating conversation: "+str(e), RNS.LOG_ERROR)

View file

@ -92,18 +92,13 @@ class ReticulumTelephone():
self.telephone.teardown() self.telephone.teardown()
self.telephone = None self.telephone = None
def hangup(self): self.telephone.hangup()
def answer(self): self.telephone.answer(self.caller)
def set_busy(self, busy): self.telephone.set_busy(busy)
def dial(self, identity_hash): def dial(self, identity_hash):
self.last_dialled_identity_hash = identity_hash self.last_dialled_identity_hash = identity_hash
self.telephone.set_busy(True)
identity_hash = bytes.fromhex(identity_hash)
destination_hash = RNS.Destination.hash_from_name_and_identity("lxst.telephony", identity_hash) destination_hash = RNS.Destination.hash_from_name_and_identity("lxst.telephony", identity_hash)
if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash)
def spincheck(): return RNS.Transport.has_path(destination_hash)
self.__spin(spincheck, "Requesting path for call to "+RNS.prettyhexrep(identity_hash), self.path_time)
if not spincheck(): RNS.log("Path request timed out", RNS.LOG_DEBUG)
self.telephone.set_busy(False)
if RNS.Transport.has_path(destination_hash): if RNS.Transport.has_path(destination_hash):
call_hops = RNS.Transport.hops_to(destination_hash) call_hops = RNS.Transport.hops_to(destination_hash)
cs = "" if call_hops == 1 else "s" cs = "" if call_hops == 1 else "s"
@ -111,7 +106,7 @@ class ReticulumTelephone():
identity = RNS.Identity.recall(destination_hash) identity = RNS.Identity.recall(destination_hash)
self.call(identity) self.call(identity)
else: else:
pass return "no_path"
def redial(self, args=None): def redial(self, args=None):
if self.last_dialled_identity_hash: self.dial(self.last_dialled_identity_hash) if self.last_dialled_identity_hash: self.dial(self.last_dialled_identity_hash)

View file

@ -6,6 +6,7 @@ from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, BooleanProperty from kivy.properties import StringProperty, BooleanProperty
from kivymd.uix.list import MDList, IconLeftWidget, IconRightWidget, OneLineAvatarIconListItem from kivymd.uix.list import MDList, IconLeftWidget, IconRightWidget, OneLineAvatarIconListItem
from kivymd.uix.menu import MDDropdownMenu from kivymd.uix.menu import MDDropdownMenu
from kivymd.toast import toast
from kivy.uix.gridlayout import GridLayout from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock from kivy.clock import Clock
@ -53,6 +54,7 @@ class Conversations():
self.app.root.ids.screen_manager.add_widget(self.screen) self.app.root.ids.screen_manager.add_widget(self.screen)
self.conversation_dropdown = None self.conversation_dropdown = None
self.voice_dropdown = None
self.delete_dialog = None self.delete_dialog = None
self.clear_dialog = None self.clear_dialog = None
self.clear_telemetry_dialog = None self.clear_telemetry_dialog = None
@ -91,6 +93,7 @@ class Conversations():
self.app.sideband.setstate("wants.viewupdate.conversations", False) self.app.sideband.setstate("wants.viewupdate.conversations", False)
def trust_icon(self, conv): def trust_icon(self, conv):
conv_type = conv["type"]
context_dest = conv["dest"] context_dest = conv["dest"]
unread = conv["unread"] unread = conv["unread"]
appearance = self.app.sideband.peer_appearance(context_dest, conv=conv) appearance = self.app.sideband.peer_appearance(context_dest, conv=conv)
@ -105,6 +108,9 @@ class Conversations():
else: else:
trust_icon = appearance[0] or da[0]; trust_icon = appearance[0] or da[0];
else:
if conv_type == self.app.sideband.CONV_VOICE:
trust_icon = "phone"
else: else:
if self.app.sideband.requests_allowed_from(context_dest): if self.app.sideband.requests_allowed_from(context_dest):
if unread: if unread:
@ -166,6 +172,7 @@ class Conversations():
iconl._default_icon_pad = dp(ic_p) iconl._default_icon_pad = dp(ic_p)
iconl.icon_size = dp(ic_s) iconl.icon_size = dp(ic_s)
iconl.conv_type = conv["type"]
return iconl return iconl
@ -187,6 +194,7 @@ class Conversations():
for conv in self.context_dests: for conv in self.context_dests:
context_dest = conv["dest"] context_dest = conv["dest"]
conv_type = conv["type"]
unread = conv["unread"] unread = conv["unread"]
last_activity = conv["last_activity"] last_activity = conv["last_activity"]
@ -203,6 +211,7 @@ class Conversations():
item.sb_uid = context_dest item.sb_uid = context_dest
item.sb_unread = unread item.sb_unread = unread
iconl.sb_uid = context_dest iconl.sb_uid = context_dest
item.conv_type = conv_type
def gen_edit(item): def gen_edit(item):
def x(): def x():
@ -366,23 +375,58 @@ class Conversations():
self.delete_dialog.open() self.delete_dialog.open()
return x 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 gen_copy_addr(item):
def x(): def x():
Clipboard.copy(RNS.hexrep(self.conversation_dropdown.context_dest, delimit=False)) Clipboard.copy(RNS.hexrep(self.conversation_dropdown.context_dest, delimit=False))
self.voice_dropdown.dismiss()
self.conversation_dropdown.dismiss()
return x
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")
item.dmenu.dismiss() item.dmenu.dismiss()
return x return x
item.iconr = IconRightWidget(icon="dots-vertical"); 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: if self.conversation_dropdown == None:
obj_str = "conversations" if is_object else "objects"
dmi_h = 40 dmi_h = 40
dm_items = [ dm_items = [
{ {
@ -391,18 +435,18 @@ class Conversations():
"height": dp(dmi_h), "height": dp(dmi_h),
"on_release": gen_edit(item) "on_release": gen_edit(item)
}, },
{
"viewclass": "OneLineListItem",
"text": "Call",
"height": dp(dmi_h),
"on_release": gen_call(item)
},
{ {
"text": "Copy Address", "text": "Copy Address",
"viewclass": "OneLineListItem", "viewclass": "OneLineListItem",
"height": dp(dmi_h), "height": dp(dmi_h),
"on_release": gen_copy_addr(item) "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", "text": "Clear Messages",
"viewclass": "OneLineListItem", "viewclass": "OneLineListItem",
@ -434,11 +478,15 @@ class Conversations():
self.conversation_dropdown.effect_cls = ScrollEffect self.conversation_dropdown.effect_cls = ScrollEffect
self.conversation_dropdown.md_bg_color = self.app.color_hover 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 item.dmenu = self.conversation_dropdown
def callback_factory(ref, dest): def callback_factory(ref, dest):
def x(sender): def x(sender):
self.conversation_dropdown.context_dest = dest self.conversation_dropdown.context_dest = dest
self.voice_dropdown.context_dest = dest
ref.dmenu.caller = ref.iconr ref.dmenu.caller = ref.iconr
ref.dmenu.open() ref.dmenu.open()
return x return x
@ -448,6 +496,7 @@ class Conversations():
item.add_widget(item.iconr) item.add_widget(item.iconr)
item.trusted = self.app.sideband.is_trusted(context_dest, conv_data=existing_conv) 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.added_item_dests.append(context_dest)
self.list.add_widget(item) self.list.add_widget(item)
@ -519,7 +568,7 @@ Builder.load_string("""
orientation: "vertical" orientation: "vertical"
spacing: "24dp" spacing: "24dp"
size_hint_y: None size_hint_y: None
height: dp(250) height: dp(260)
MDTextField: MDTextField:
id: n_address_field id: n_address_field
@ -540,7 +589,7 @@ Builder.load_string("""
orientation: "horizontal" orientation: "horizontal"
size_hint_y: None size_hint_y: None
padding: [0,0,dp(8),dp(24)] padding: [0,0,dp(8),dp(24)]
height: dp(48) height: dp(24)
MDLabel: MDLabel:
id: "trusted_switch_label" id: "trusted_switch_label"
text: "Trusted" text: "Trusted"
@ -551,6 +600,21 @@ Builder.load_string("""
pos_hint: {"center_y": 0.3} pos_hint: {"center_y": 0.3}
active: False active: False
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
padding: [0,0,dp(8),dp(24)]
height: dp(24)
MDLabel:
id: "trusted_switch_label"
text: "Voice Only"
font_style: "H6"
MDSwitch:
id: n_voice_only
pos_hint: {"center_y": 0.3}
active: False
<ConvSettings> <ConvSettings>
orientation: "vertical" orientation: "vertical"
spacing: "16dp" spacing: "16dp"

View file

@ -30,9 +30,10 @@ class Voice():
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
self.screen = None self.screen = None
self.rnstatus_screen = None self.settings_screen = None
self.rnstatus_instance = None self.dial_target = None
self.logviewer_screen = None self.ui_updater = None
self.path_requesting = None
if not self.app.root.ids.screen_manager.has_screen("voice_screen"): if not self.app.root.ids.screen_manager.has_screen("voice_screen"):
self.screen = Builder.load_string(layout_voice_screen) self.screen = Builder.load_string(layout_voice_screen)
@ -41,13 +42,131 @@ class Voice():
self.app.root.ids.screen_manager.add_widget(self.screen) self.app.root.ids.screen_manager.add_widget(self.screen)
self.screen.ids.voice_scrollview.effect_cls = ScrollEffect self.screen.ids.voice_scrollview.effect_cls = ScrollEffect
info = "Voice services UI" # info = "Voice services UI"
info += "" # info += ""
if self.app.theme_cls.theme_style == "Dark": # if self.app.theme_cls.theme_style == "Dark":
info = "[color=#"+self.app.dark_theme_text_color+"]"+info+"[/color]" # info = "[color=#"+self.app.dark_theme_text_color+"]"+info+"[/color]"
self.screen.ids.voice_info.text = info # self.screen.ids.voice_info.text = info
def update_call_status(self, dt=None):
if self.app.root.ids.screen_manager.current == "voice_screen":
if self.ui_updater == None: self.ui_updater = Clock.schedule_interval(self.update_call_status, 0.5)
else:
if self.ui_updater: self.ui_updater.cancel()
db = self.screen.ids.dial_button
ih = self.screen.ids.identity_hash
if self.app.sideband.voice_running:
telephone = self.app.sideband.telephone
if self.path_requesting:
db.disabled = True
ih.disabled = True
else:
if telephone.is_available:
ih.disabled = False
self.target_input_action(ih)
else:
ih.disabled = True
if telephone.is_in_call or telephone.call_is_connecting:
ih.disabled = True
db.disabled = False
db.text = "Hang up"
db.icon = "phone-hangup"
elif telephone.is_ringing:
ih.disabled = True
db.disabled = False
db.text = "Answer"
db.icon = "phone-ring"
if telephone.caller: ih.text = RNS.hexrep(telephone.caller.hash, delimit=False)
else:
db.disabled = True; db.text = "Voice calls disabled"
ih.disabled = True
def target_valid(self):
if self.app.sideband.voice_running:
db = self.screen.ids.dial_button
db.disabled = False; db.text = "Call"
db.icon = "phone-outgoing"
def target_invalid(self):
if self.app.sideband.voice_running:
db = self.screen.ids.dial_button
db.disabled = True; db.text = "Call"
db.icon = "phone-outgoing"
def target_input_action(self, sender):
if sender:
target_hash = sender.text
if len(target_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8*2:
try:
identity_hash = bytes.fromhex(target_hash)
self.dial_target = identity_hash
self.target_valid()
except Exception as e: self.target_invalid()
else: self.target_invalid()
def request_path(self, destination_hash):
if not self.path_requesting:
self.app.sideband.telephone.set_busy(True)
toast("Requesting path...")
self.screen.ids.dial_button.disabled = True
self.path_requesting = destination_hash
RNS.Transport.request_path(destination_hash)
threading.Thread(target=self._path_wait_job, daemon=True).start()
else:
toast("Waiting for path request answer...")
def _path_wait_job(self):
timeout = time.time()+self.app.sideband.telephone.PATH_TIME
while not RNS.Transport.has_path(self.path_requesting) and time.time() < timeout:
time.sleep(0.25)
self.app.sideband.telephone.set_busy(False)
if RNS.Transport.has_path(self.path_requesting):
RNS.log(f"Calling {RNS.prettyhexrep(self.dial_target)}...", RNS.LOG_DEBUG)
self.app.sideband.telephone.dial(self.dial_target)
Clock.schedule_once(self.update_call_status, 0.1)
else:
Clock.schedule_once(self._path_request_failed, 0.05)
Clock.schedule_once(self.update_call_status, 0.1)
self.path_requesting = None
self.update_call_status()
def _path_request_failed(self, dt):
toast("Path request timed out")
def dial_action(self, sender=None):
if self.app.sideband.voice_running:
if self.app.sideband.telephone.is_available:
destination_hash = RNS.Destination.hash_from_name_and_identity("lxst.telephony", self.dial_target)
if not RNS.Transport.has_path(destination_hash):
self.request_path(destination_hash)
else:
RNS.log(f"Calling {RNS.prettyhexrep(self.dial_target)}...", RNS.LOG_DEBUG)
self.app.sideband.telephone.dial(self.dial_target)
self.update_call_status()
elif self.app.sideband.telephone.is_in_call or self.app.sideband.telephone.call_is_connecting:
RNS.log(f"Hanging up", RNS.LOG_DEBUG)
self.app.sideband.telephone.hangup()
self.update_call_status()
elif self.app.sideband.telephone.is_ringing:
RNS.log(f"Answering", RNS.LOG_DEBUG)
self.app.sideband.telephone.answer()
self.update_call_status()
layout_voice_screen = """ layout_voice_screen = """
MDScreen: MDScreen:
@ -76,17 +195,21 @@ MDScreen:
height: self.minimum_height height: self.minimum_height
padding: [dp(28), dp(32), dp(28), dp(16)] padding: [dp(28), dp(32), dp(28), dp(16)]
# MDLabel: MDBoxLayout:
# text: "Utilities & Tools" orientation: "vertical"
# font_style: "H6" # spacing: "24dp"
MDLabel:
id: voice_info
markup: True
text: ""
size_hint_y: None size_hint_y: None
text_size: self.width, None height: self.minimum_height
height: self.texture_size[1] padding: [dp(0), dp(12), dp(0), dp(0)]
MDTextField:
id: identity_hash
hint_text: "Identity hash"
mode: "rectangle"
# size_hint: [1.0, None]
pos_hint: {"center_x": .5}
max_text_length: 32
on_text: root.delegate.target_input_action(self)
MDBoxLayout: MDBoxLayout:
orientation: "vertical" orientation: "vertical"
@ -96,13 +219,13 @@ MDScreen:
padding: [dp(0), dp(35), dp(0), dp(35)] padding: [dp(0), dp(35), dp(0), dp(35)]
MDRectangleFlatIconButton: MDRectangleFlatIconButton:
id: rnstatus_button id: dial_button
icon: "wifi-check" icon: "phone-outgoing"
text: "Reticulum Status" text: "Call"
padding: [dp(0), dp(14), dp(0), dp(14)] padding: [dp(0), dp(14), dp(0), dp(14)]
icon_size: dp(24) icon_size: dp(24)
font_size: dp(16) font_size: dp(16)
size_hint: [1.0, None] size_hint: [1.0, None]
on_release: root.delegate.rnstatus_action(self) on_release: root.delegate.dial_action(self)
disabled: False disabled: True
""" """