From 3b51426bcccd90bc2f19ca34e01224dac978de52 Mon Sep 17 00:00:00 2001 From: Alisya Kainth Date: Thu, 22 May 2025 22:47:44 -0400 Subject: [PATCH 1/6] arr --- components/mesh_conn/MeshConn.py | 32 +++++++++++++++++ components/mesh_conn/__init__.py | 0 components/tui/chat_window/ChatWindow.py | 31 ++++++++++++++++ components/tui/chat_window/__init__.py | 0 components/tui/mesh_app/MeshApp.py | 45 ++++++++++++++++++++++++ components/tui/mesh_app/__init__.py | 0 main.py | 23 ++---------- 7 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 components/mesh_conn/MeshConn.py create mode 100644 components/mesh_conn/__init__.py create mode 100644 components/tui/chat_window/ChatWindow.py create mode 100644 components/tui/chat_window/__init__.py create mode 100644 components/tui/mesh_app/MeshApp.py create mode 100644 components/tui/mesh_app/__init__.py diff --git a/components/mesh_conn/MeshConn.py b/components/mesh_conn/MeshConn.py new file mode 100644 index 0000000..890ee06 --- /dev/null +++ b/components/mesh_conn/MeshConn.py @@ -0,0 +1,32 @@ +# api for meshtastic cli +import meshtastic +import meshtastic.serial_interface +from pubsub import pub +import time + + +class MeshConn: + def __init__(self): + """ + Connect to a meshtastic radio through a serial port. + """ + pub.subscribe(self.on_connection, "meshtastic.connection.established") + + self.interface = meshtastic.serial_interface.SerialInterface() + self.nodes = self.interface.nodes + + + def on_connection(self, interface, topic=pub.AUTO_TOPIC): + print("Connected to mesh") + + + def get_long_names(self): + """ + Retrieve the names of all other nodes in the mesh. + Takes like 10s to run for > 300 nodes lol. + """ + metadata = self.nodes.values() + long_names = [ node['user']['longName'] for node in metadata] + + return long_names + diff --git a/components/mesh_conn/__init__.py b/components/mesh_conn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/components/tui/chat_window/ChatWindow.py b/components/tui/chat_window/ChatWindow.py new file mode 100644 index 0000000..9a28229 --- /dev/null +++ b/components/tui/chat_window/ChatWindow.py @@ -0,0 +1,31 @@ +from textual.widgets import Static, Input +from textual.containers import Vertical +from textual.app import ComposeResult + + +class ChatWindow(Vertical): + """ + A chat window for a selected contact. + """ + + def __init__(self, longname: str, **kwargs): + super().__init__(**kwargs) + self.longname = longname + self.messages = [] + self.message_display = Static() + self.input_box = Input(placeholder="Type a message and press Enter...") + self.input_box.border_title = f"Chat with {longname}" + + def compose(self) -> ComposeResult: + yield self.message_display + yield self.input_box + + def on_input_submitted(self, event: Input.Submitted) -> None: + message = event.value.strip() + if message: + self.messages.append(f"You: {message}") + self.update_display() + self.input_box.value = "" + + def update_display(self): + self.message_display.update("\n".join(self.messages)) diff --git a/components/tui/chat_window/__init__.py b/components/tui/chat_window/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/components/tui/mesh_app/MeshApp.py b/components/tui/mesh_app/MeshApp.py new file mode 100644 index 0000000..9012cd3 --- /dev/null +++ b/components/tui/mesh_app/MeshApp.py @@ -0,0 +1,45 @@ +from textual.widgets import Header, Footer, ListView, ListItem, Label +from textual.containers import Horizontal +from textual.app import App, ComposeResult + +from components.mesh_conn.MeshConn import MeshConn +from components.tui.chat_window.ChatWindow import ChatWindow + + +class MeshApp(App): + """ + Main app. + """ + + BINDINGS = [("d", "toggle_dark", "Toggle dark mode")] + + def __init__(self): + super().__init__() + self.conn = MeshConn() + self.chat_window = ChatWindow("Select a contact") + self.contact_list = ListView() + + def compose(self) -> ComposeResult: + yield Header() + with Horizontal(): + yield self.contact_list + yield self.chat_window + yield Footer() + + async def on_mount(self) -> None: + """Populate the ListView after widgets are mounted.""" + for name in self.conn.get_long_names(): + await self.contact_list.append(ListItem(Label(name))) + + def action_toggle_dark(self) -> None: + self.theme = ( + "textual-dark" if self.theme == "textual-light" else "textual-light" + ) + + async def on_list_view_selected(self, message: ListView.Selected) -> None: + selected_label = message.item.query_one(Label) + name = str(selected_label.renderable) + self.chat_window.longname = name + self.chat_window.input_box.border_title = f"Chat with {name}" + self.chat_window.messages = [] + self.chat_window.update_display() diff --git a/components/tui/mesh_app/__init__.py b/components/tui/mesh_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py index 4c94af3..12db4e8 100644 --- a/main.py +++ b/main.py @@ -1,24 +1,5 @@ -from textual.app import App, ComposeResult -from textual.widgets import Footer, Header - - -class ExampleMeshApp(App): - """A Textual app to manage stopwatches.""" - - BINDINGS = [("d", "toggle_dark", "Toggle dark mode")] - - def compose(self) -> ComposeResult: - """Create child widgets for the app.""" - yield Header() - yield Footer() - - def action_toggle_dark(self) -> None: - """An action to toggle dark mode.""" - self.theme = ( - "textual-dark" if self.theme == "textual-light" else "textual-light" - ) - +from components.tui.mesh_app.MeshApp import MeshApp if __name__ == "__main__": - app = ExampleMeshApp() + app = MeshApp() app.run() From b45a5ec3d1bab8a8bfc4878e24c1997feeb1c77b Mon Sep 17 00:00:00 2001 From: Alisya Kainth Date: Fri, 23 May 2025 15:46:50 -0400 Subject: [PATCH 2/6] can tx but no rx lol --- components/mesh_conn/MeshConn.py | 11 ++++++++--- components/tui/chat_window/ChatWindow.py | 5 ++++- components/tui/mesh_app/MeshApp.py | 5 ++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/components/mesh_conn/MeshConn.py b/components/mesh_conn/MeshConn.py index 890ee06..4d62fa3 100644 --- a/components/mesh_conn/MeshConn.py +++ b/components/mesh_conn/MeshConn.py @@ -14,6 +14,9 @@ def __init__(self): self.interface = meshtastic.serial_interface.SerialInterface() self.nodes = self.interface.nodes + + # create dictionary for long names and radio ids {"longName":"id"} + self.name_id_map = { metadata['user']['longName'] : id for id, metadata in self.nodes.items()} def on_connection(self, interface, topic=pub.AUTO_TOPIC): @@ -25,8 +28,10 @@ def get_long_names(self): Retrieve the names of all other nodes in the mesh. Takes like 10s to run for > 300 nodes lol. """ - metadata = self.nodes.values() - long_names = [ node['user']['longName'] for node in metadata] - + long_names = self.name_id_map.keys() return long_names + + async def send_message(self, longname: str, message: str): + id = self.name_id_map[longname] + self.interface.sendText(message, destinationId=id) diff --git a/components/tui/chat_window/ChatWindow.py b/components/tui/chat_window/ChatWindow.py index 9a28229..abf1070 100644 --- a/components/tui/chat_window/ChatWindow.py +++ b/components/tui/chat_window/ChatWindow.py @@ -8,10 +8,11 @@ class ChatWindow(Vertical): A chat window for a selected contact. """ - def __init__(self, longname: str, **kwargs): + def __init__(self, longname: str, send_callback=None, **kwargs): super().__init__(**kwargs) self.longname = longname self.messages = [] + self.send_callback = send_callback self.message_display = Static() self.input_box = Input(placeholder="Type a message and press Enter...") self.input_box.border_title = f"Chat with {longname}" @@ -26,6 +27,8 @@ def on_input_submitted(self, event: Input.Submitted) -> None: self.messages.append(f"You: {message}") self.update_display() self.input_box.value = "" + if self.send_callback and self.longname: + self.app.call_later(self.send_callback, self.longname, message) def update_display(self): self.message_display.update("\n".join(self.messages)) diff --git a/components/tui/mesh_app/MeshApp.py b/components/tui/mesh_app/MeshApp.py index 9012cd3..ca7eec5 100644 --- a/components/tui/mesh_app/MeshApp.py +++ b/components/tui/mesh_app/MeshApp.py @@ -16,7 +16,7 @@ class MeshApp(App): def __init__(self): super().__init__() self.conn = MeshConn() - self.chat_window = ChatWindow("Select a contact") + self.chat_window = ChatWindow("Select a contact", send_callback=self.send_message) self.contact_list = ListView() def compose(self) -> ComposeResult: @@ -43,3 +43,6 @@ async def on_list_view_selected(self, message: ListView.Selected) -> None: self.chat_window.input_box.border_title = f"Chat with {name}" self.chat_window.messages = [] self.chat_window.update_display() + + async def send_message(self, longname: str, message: str): + await self.conn.send_message(longname, message) From e79847dbafe1c838e370dbb6ab3bf02eeb381df7 Mon Sep 17 00:00:00 2001 From: Alisya Kainth Date: Mon, 9 Jun 2025 23:07:00 -0400 Subject: [PATCH 3/6] receive working --- components/mesh_conn/MeshConn.py | 35 ++++++++++++++++++------ components/tui/chat_window/ChatWindow.py | 3 +- components/tui/mesh_app/MeshApp.py | 10 ++++++- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/components/mesh_conn/MeshConn.py b/components/mesh_conn/MeshConn.py index 4d62fa3..c52f9dd 100644 --- a/components/mesh_conn/MeshConn.py +++ b/components/mesh_conn/MeshConn.py @@ -6,27 +6,46 @@ class MeshConn: - def __init__(self): + def __init__(self, on_message_callback=None): """ Connect to a meshtastic radio through a serial port. """ - pub.subscribe(self.on_connection, "meshtastic.connection.established") + self.on_message_callback = on_message_callback + pub.subscribe(self.on_receive, "meshtastic.receive") self.interface = meshtastic.serial_interface.SerialInterface() - self.nodes = self.interface.nodes + self.nodes = self.interface.nodes.items() + + self.this_node = self.interface.getMyNodeInfo() # create dictionary for long names and radio ids {"longName":"id"} - self.name_id_map = { metadata['user']['longName'] : id for id, metadata in self.nodes.items()} + self.name_id_map = { metadata['user']['longName'] : id for id, metadata in self.nodes} + + # another one for the reverse {"id": "longName"} + self.id_name_map = { id : metadata['user']['longName'] for id, metadata in self.nodes} - def on_connection(self, interface, topic=pub.AUTO_TOPIC): - print("Connected to mesh") + def on_receive(self, packet, interface): + """ + Receive a message sent to our radio. + """ + try: + to_id = packet.get("toId") + from_id = packet.get("fromId") + message = packet["decoded"]["text"] + this_id = self.this_node['user']['id'] + + if to_id == this_id or to_id == "^all": + sender_name = self.id_name_map.get(from_id, "Unknown") + if self.on_message_callback: + self.on_message_callback(sender_name, message) + except Exception as e: + print(f"[ERROR on_receive] {e}") def get_long_names(self): """ - Retrieve the names of all other nodes in the mesh. - Takes like 10s to run for > 300 nodes lol. + Retrieve the names of all other nodes in the mesh """ long_names = self.name_id_map.keys() return long_names diff --git a/components/tui/chat_window/ChatWindow.py b/components/tui/chat_window/ChatWindow.py index abf1070..1d5a032 100644 --- a/components/tui/chat_window/ChatWindow.py +++ b/components/tui/chat_window/ChatWindow.py @@ -15,7 +15,8 @@ def __init__(self, longname: str, send_callback=None, **kwargs): self.send_callback = send_callback self.message_display = Static() self.input_box = Input(placeholder="Type a message and press Enter...") - self.input_box.border_title = f"Chat with {longname}" + self.input_wrapper = Static(self.input_box) + self.input_wrapper.border_title = f"Chat with {longname}" def compose(self) -> ComposeResult: yield self.message_display diff --git a/components/tui/mesh_app/MeshApp.py b/components/tui/mesh_app/MeshApp.py index ca7eec5..b0ca4b6 100644 --- a/components/tui/mesh_app/MeshApp.py +++ b/components/tui/mesh_app/MeshApp.py @@ -15,9 +15,17 @@ class MeshApp(App): def __init__(self): super().__init__() - self.conn = MeshConn() + self.conn = MeshConn(on_message_callback=self.handle_incoming_message) self.chat_window = ChatWindow("Select a contact", send_callback=self.send_message) self.contact_list = ListView() + + def handle_incoming_message(self, longname, message): + self.call_later(self._display_incoming_message, longname, message) + + async def _display_incoming_message(self, longname, message): + if self.chat_window.longname == longname: + self.chat_window.messages.append(f"{longname}: {message}") + self.chat_window.update_display() def compose(self) -> ComposeResult: yield Header() From aa6e51b82f5bf3e846a6be1a340559f61fb7a187 Mon Sep 17 00:00:00 2001 From: Alisya Kainth Date: Mon, 9 Jun 2025 23:56:16 -0400 Subject: [PATCH 4/6] prettier api --- components/tui/chat_window/ChatWindow.py | 4 +++- components/tui/mesh_app/MeshApp.py | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/tui/chat_window/ChatWindow.py b/components/tui/chat_window/ChatWindow.py index 1d5a032..f0f76ed 100644 --- a/components/tui/chat_window/ChatWindow.py +++ b/components/tui/chat_window/ChatWindow.py @@ -1,6 +1,7 @@ from textual.widgets import Static, Input from textual.containers import Vertical from textual.app import ComposeResult +from datetime import datetime class ChatWindow(Vertical): @@ -25,7 +26,8 @@ def compose(self) -> ComposeResult: def on_input_submitted(self, event: Input.Submitted) -> None: message = event.value.strip() if message: - self.messages.append(f"You: {message}") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.messages.append(f" [dim]{timestamp}[/dim]\n[blue] You[/blue]: {message}\n") self.update_display() self.input_box.value = "" if self.send_callback and self.longname: diff --git a/components/tui/mesh_app/MeshApp.py b/components/tui/mesh_app/MeshApp.py index b0ca4b6..b88f844 100644 --- a/components/tui/mesh_app/MeshApp.py +++ b/components/tui/mesh_app/MeshApp.py @@ -4,6 +4,7 @@ from components.mesh_conn.MeshConn import MeshConn from components.tui.chat_window.ChatWindow import ChatWindow +from datetime import datetime class MeshApp(App): @@ -24,11 +25,14 @@ def handle_incoming_message(self, longname, message): async def _display_incoming_message(self, longname, message): if self.chat_window.longname == longname: - self.chat_window.messages.append(f"{longname}: {message}") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.chat_window.messages.append(f" [dim]{timestamp}[/dim]\n[red] {longname}[/red]: {message}\n") self.chat_window.update_display() def compose(self) -> ComposeResult: yield Header() + self.contact_list.styles.width = "35%" + self.chat_window.styles.width = "65%" with Horizontal(): yield self.contact_list yield self.chat_window From ca1ecd2a59e575c1ae7b5198f46ce5ecab0d0d89 Mon Sep 17 00:00:00 2001 From: Alisya Kainth Date: Sun, 29 Jun 2025 14:56:20 -0400 Subject: [PATCH 5/6] add db --- .gitignore | 3 +++ components/data/ChatDB.py | 37 ++++++++++++++++++++++++++++++ components/data/__init__.py | 0 components/mesh_conn/MeshConn.py | 14 +++++++++-- components/tui/mesh_app/MeshApp.py | 11 +++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 components/data/ChatDB.py create mode 100644 components/data/__init__.py diff --git a/.gitignore b/.gitignore index fe92647..d9dd814 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ wheels/ # Virtual environments .venv + +# SQLite Database +*.db diff --git a/components/data/ChatDB.py b/components/data/ChatDB.py new file mode 100644 index 0000000..cd1e801 --- /dev/null +++ b/components/data/ChatDB.py @@ -0,0 +1,37 @@ +import sqlite3 +from datetime import datetime + +class ChatDB: + def __init__(self, db_path="chat_history.db"): + self.conn = sqlite3.connect(db_path) + self.create_table() + + def create_table(self): + self.conn.execute(""" + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + contact TEXT NOT NULL, + sender TEXT NOT NULL, + message TEXT NOT NULL, + timestamp TEXT NOT NULL + ) + """) + self.conn.commit() + + def save_message(self, contact, sender, message): + timestamp = datetime.now().isoformat() + self.conn.execute( + "INSERT INTO messages (contact, sender, message, timestamp) VALUES (?, ?, ?, ?)", + (contact, sender, message, timestamp) + ) + self.conn.commit() + + def load_messages(self, contact): + cursor = self.conn.execute( + "SELECT sender, message, timestamp FROM messages WHERE contact = ? ORDER BY timestamp", + (contact,) + ) + return cursor.fetchall() + + def close(self): + self.conn.close() diff --git a/components/data/__init__.py b/components/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/components/mesh_conn/MeshConn.py b/components/mesh_conn/MeshConn.py index c52f9dd..080b121 100644 --- a/components/mesh_conn/MeshConn.py +++ b/components/mesh_conn/MeshConn.py @@ -3,6 +3,7 @@ import meshtastic.serial_interface from pubsub import pub import time +import sys class MeshConn: @@ -13,8 +14,17 @@ def __init__(self, on_message_callback=None): self.on_message_callback = on_message_callback pub.subscribe(self.on_receive, "meshtastic.receive") - self.interface = meshtastic.serial_interface.SerialInterface() - self.nodes = self.interface.nodes.items() + try: + self.interface = meshtastic.serial_interface.SerialInterface() + except Exception as e: + print("ERROR: There was an error connecting to the radio.", e) + sys.exit(1) + + try: + self.nodes = self.interface.nodes.items() + except AttributeError as e: + print("ERROR: It is likely that there is no radio connected.", e) + sys.exit(1) self.this_node = self.interface.getMyNodeInfo() diff --git a/components/tui/mesh_app/MeshApp.py b/components/tui/mesh_app/MeshApp.py index b88f844..c646317 100644 --- a/components/tui/mesh_app/MeshApp.py +++ b/components/tui/mesh_app/MeshApp.py @@ -2,6 +2,7 @@ from textual.containers import Horizontal from textual.app import App, ComposeResult +from components.data.ChatDB import ChatDB from components.mesh_conn.MeshConn import MeshConn from components.tui.chat_window.ChatWindow import ChatWindow from datetime import datetime @@ -16,6 +17,7 @@ class MeshApp(App): def __init__(self): super().__init__() + self.db = ChatDB() self.conn = MeshConn(on_message_callback=self.handle_incoming_message) self.chat_window = ChatWindow("Select a contact", send_callback=self.send_message) self.contact_list = ListView() @@ -28,6 +30,7 @@ async def _display_incoming_message(self, longname, message): timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.chat_window.messages.append(f" [dim]{timestamp}[/dim]\n[red] {longname}[/red]: {message}\n") self.chat_window.update_display() + self.db.save_message(longname, longname, message) def compose(self) -> ComposeResult: yield Header() @@ -54,7 +57,15 @@ async def on_list_view_selected(self, message: ListView.Selected) -> None: self.chat_window.longname = name self.chat_window.input_box.border_title = f"Chat with {name}" self.chat_window.messages = [] + + for sender, msg, timestamp in self.db.load_messages(name): + if sender == "You": + self.chat_window.messages.append(f" [dim]{timestamp}[/dim]\n[blue] You[/blue]: {msg}\n") + else: + self.chat_window.messages.append(f" [dim]{timestamp}[/dim]\n[red] {sender}[/red]: {message}\n") + self.chat_window.update_display() async def send_message(self, longname: str, message: str): await self.conn.send_message(longname, message) + self.db.save_message(longname, "You", message) From 1c618051088912bb846c6e4cd3a8d025b4f28223 Mon Sep 17 00:00:00 2001 From: Alisya Kainth Date: Sun, 29 Jun 2025 16:43:52 -0400 Subject: [PATCH 6/6] make chat scrollable --- components/mesh_conn/MeshConn.py | 7 ++++++- components/tui/chat_window/ChatWindow.py | 20 ++++++++++++-------- components/tui/mesh_app/MeshApp.py | 9 +++++++-- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/components/mesh_conn/MeshConn.py b/components/mesh_conn/MeshConn.py index 080b121..934679d 100644 --- a/components/mesh_conn/MeshConn.py +++ b/components/mesh_conn/MeshConn.py @@ -34,8 +34,13 @@ def __init__(self, on_message_callback=None): # another one for the reverse {"id": "longName"} self.id_name_map = { id : metadata['user']['longName'] for id, metadata in self.nodes} + def get_this_node(self): + """ + Return the name of our radio. + """ + return self.this_node['user']['longName'] - def on_receive(self, packet, interface): + def on_receive(self, packet): """ Receive a message sent to our radio. """ diff --git a/components/tui/chat_window/ChatWindow.py b/components/tui/chat_window/ChatWindow.py index f0f76ed..75e1eae 100644 --- a/components/tui/chat_window/ChatWindow.py +++ b/components/tui/chat_window/ChatWindow.py @@ -1,28 +1,30 @@ from textual.widgets import Static, Input -from textual.containers import Vertical +from textual.containers import Vertical, VerticalScroll from textual.app import ComposeResult from datetime import datetime class ChatWindow(Vertical): - """ - A chat window for a selected contact. - """ - def __init__(self, longname: str, send_callback=None, **kwargs): super().__init__(**kwargs) self.longname = longname self.messages = [] self.send_callback = send_callback + + self.message_scroll = VerticalScroll() + self.message_scroll.styles.width = "100%" + self.message_display = Static() + self.input_box = Input(placeholder="Type a message and press Enter...") - self.input_wrapper = Static(self.input_box) - self.input_wrapper.border_title = f"Chat with {longname}" def compose(self) -> ComposeResult: - yield self.message_display + yield self.message_scroll yield self.input_box + async def on_mount(self): + await self.message_scroll.mount(self.message_display) + def on_input_submitted(self, event: Input.Submitted) -> None: message = event.value.strip() if message: @@ -35,3 +37,5 @@ def on_input_submitted(self, event: Input.Submitted) -> None: def update_display(self): self.message_display.update("\n".join(self.messages)) + self.message_scroll.scroll_end(animate=False) + \ No newline at end of file diff --git a/components/tui/mesh_app/MeshApp.py b/components/tui/mesh_app/MeshApp.py index c646317..b0a5d71 100644 --- a/components/tui/mesh_app/MeshApp.py +++ b/components/tui/mesh_app/MeshApp.py @@ -1,4 +1,4 @@ -from textual.widgets import Header, Footer, ListView, ListItem, Label +from textual.widgets import Header, Footer, ListView, ListItem, Label, Static from textual.containers import Horizontal from textual.app import App, ComposeResult @@ -21,6 +21,7 @@ def __init__(self): self.conn = MeshConn(on_message_callback=self.handle_incoming_message) self.chat_window = ChatWindow("Select a contact", send_callback=self.send_message) self.contact_list = ListView() + self.connection_label = Static() def handle_incoming_message(self, longname, message): self.call_later(self._display_incoming_message, longname, message) @@ -36,13 +37,16 @@ def compose(self) -> ComposeResult: yield Header() self.contact_list.styles.width = "35%" self.chat_window.styles.width = "65%" + yield self.connection_label with Horizontal(): yield self.contact_list yield self.chat_window yield Footer() async def on_mount(self) -> None: - """Populate the ListView after widgets are mounted.""" + node_name = self.conn.get_this_node() + self.connection_label.update(f"[bold green]Connected to:[/] {node_name}") + for name in self.conn.get_long_names(): await self.contact_list.append(ListItem(Label(name))) @@ -59,6 +63,7 @@ async def on_list_view_selected(self, message: ListView.Selected) -> None: self.chat_window.messages = [] for sender, msg, timestamp in self.db.load_messages(name): + timestamp = timestamp[0:10] + " " + timestamp[11:19] if sender == "You": self.chat_window.messages.append(f" [dim]{timestamp}[/dim]\n[blue] You[/blue]: {msg}\n") else: