From 8ac731149d7855acf85d52f3f147fc89ce33deda Mon Sep 17 00:00:00 2001 From: miruka Date: Wed, 3 Jul 2019 21:20:49 -0400 Subject: [PATCH] Sending messages and local echo --- src/python/events/event.py | 11 +++++++++- src/python/events/rooms.py | 23 ++++++++++----------- src/python/matrix_client.py | 26 +++++++++++++++++++++++- src/qml/Chat/SendBox.qml | 6 ++++-- src/qml/Chat/Timeline/EventDelegate.qml | 21 +++++++++++-------- src/qml/EventHandlers/rooms.js | 27 +++++++++++++++++++------ 6 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/python/events/event.py b/src/python/events/event.py index 97502fbd..65208fca 100644 --- a/src/python/events/event.py +++ b/src/python/events/event.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import Any from dataclasses import dataclass @@ -17,7 +18,15 @@ class Event: # CPython >= 3.6 or any Python >= 3.7 needed for correct dict order args = [ # pylint: disable=no-member - getattr(self, field) + self._process_field(getattr(self, field)) for field in self.__dataclass_fields__ # type: ignore ] pyotherside.send(type(self).__name__, *args) + + + @staticmethod + def _process_field(value: Any) -> Any: + if hasattr(value, "__class__") and issubclass(value.__class__, Enum): + return value.value + + return value diff --git a/src/python/events/rooms.py b/src/python/events/rooms.py index d82a9c44..53ee9112 100644 --- a/src/python/events/rooms.py +++ b/src/python/events/rooms.py @@ -54,27 +54,26 @@ class ContentType(AutoStrEnum): location = auto() + + @dataclass class TimelineEventReceived(Event): - event_type: Type[nio.Event] = field() - room_id: str = field() - event_id: str = field() - sender_id: str = field() - date: datetime = field() - content: str = field() - - content_type: ContentType = ContentType.html - is_local_echo: bool = False + event_type: Type[nio.Event] = field() + room_id: str = field() + event_id: str = field() + sender_id: str = field() + date: datetime = field() + content: str = field() + content_type: ContentType = ContentType.html + is_local_echo: bool = False show_name_line: bool = False translatable: Union[bool, Sequence[str]] = True target_user_id: Optional[str] = None - @classmethod - def from_nio(cls, room: nio.rooms.MatrixRoom, ev: nio.Event, **fields - ) -> "TimelineEventReceived": + def from_nio(cls, room, ev, **fields) -> "TimelineEventReceived": return cls( event_type = type(ev), room_id = room.room_id, diff --git a/src/python/matrix_client.py b/src/python/matrix_client.py index 8ced55b3..29e85ac1 100644 --- a/src/python/matrix_client.py +++ b/src/python/matrix_client.py @@ -5,6 +5,7 @@ import json import logging as log import platform from contextlib import suppress +from datetime import datetime from types import ModuleType from typing import Dict, Optional, Type @@ -22,9 +23,10 @@ class MatrixClient(nio.AsyncClient): user: str, homeserver: str = "https://matrix.org", device_id: Optional[str] = None) -> None: - # TODO: ensure homeserver starts with a scheme:// + self.sync_task: Optional[asyncio.Future] = None + super().__init__(homeserver=homeserver, user=user, device_id=device_id) self.connect_callbacks() @@ -113,6 +115,27 @@ class MatrixClient(nio.AsyncClient): ) + async def send_markdown(self, room_id: str, text: str) -> None: + content = { + "body": text, + "formatted_body": HTML_FILTER.from_markdown(text), + "format": "org.matrix.custom.html", + "msgtype": "m.text", + } + + TimelineMessageReceived( + event_type = nio.RoomMessageText, + room_id = room_id, + event_id = "local_echo", + sender_id = self.user_id, + date = datetime.now(), + content = content["formatted_body"], + is_local_echo = True, + ) + + await self.room_send(room_id, "m.room.message", content) + + # Callbacks for nio responses @staticmethod @@ -172,6 +195,7 @@ class MatrixClient(nio.AsyncClient): ev.formatted_body if ev.format == "org.matrix.custom.html" else html.escape(ev.body) ) + TimelineMessageReceived.from_nio(room, ev, content=co) diff --git a/src/qml/Chat/SendBox.qml b/src/qml/Chat/SendBox.qml index 3a70b92d..82291086 100644 --- a/src/qml/Chat/SendBox.qml +++ b/src/qml/Chat/SendBox.qml @@ -34,6 +34,7 @@ HRectangle { property bool textChangedSinceLostFocus: false function setTyping(typing) { + return Backend.clients.get(chatPage.userId) .setTypingState(chatPage.roomId, typing) } @@ -60,8 +61,9 @@ HRectangle { } if (textArea.text === "") { return } - Backend.clients.get(chatPage.userId) - .sendMarkdown(chatPage.roomId, textArea.text) + + var args = [chatPage.roomId, textArea.text] + py.callClientCoro(chatPage.userId, "send_markdown", args) area.clear() } diff --git a/src/qml/Chat/Timeline/EventDelegate.qml b/src/qml/Chat/Timeline/EventDelegate.qml index 82e0a0fb..e5ad7e84 100644 --- a/src/qml/Chat/Timeline/EventDelegate.qml +++ b/src/qml/Chat/Timeline/EventDelegate.qml @@ -12,8 +12,8 @@ Column { function getPreviousItem(nth) { // Remember, index 0 = newest bottomest message nth = nth || 1 - return model.index + nth - 1 < roomEventListView.model.count - 1 ? - roomEventListView.model.get(index + nth) : null + return roomEventListView.model.count - 1 > model.index + nth ? + roomEventListView.model.get(model.index + nth) : null } function isMessage(item) { @@ -32,23 +32,28 @@ Column { readonly property bool isFirstEvent: model.eventType == "RoomCreateEvent" - readonly property bool combine: - previousItem && + // Item roles may not be loaded yet, reason for all these checks + readonly property bool combine: Boolean( + model.date && + previousItem && previousItem.eventType && previousItem.date && isMessage(previousItem) == isMessage(model) && ! talkBreak && ! dayBreak && previousItem.senderId === model.senderId && minsBetween(previousItem.date, model.date) <= 5 + ) - readonly property bool dayBreak: + readonly property bool dayBreak: Boolean( isFirstEvent || - previousItem && + model.date && previousItem && previousItem.date && model.date.getDate() != previousItem.date.getDate() + ) - readonly property bool talkBreak: - previousItem && + readonly property bool talkBreak: Boolean( + model.date && previousItem && previousItem.date && ! dayBreak && minsBetween(previousItem.date, model.date) >= 20 + ) property int standardSpacing: 16 diff --git a/src/qml/EventHandlers/rooms.js b/src/qml/EventHandlers/rooms.js index 93947629..bac39aa0 100644 --- a/src/qml/EventHandlers/rooms.js +++ b/src/qml/EventHandlers/rooms.js @@ -43,7 +43,6 @@ function onRoomUpdated(user_id, category, room_id, display_name, avatar_url, "inviter": inviter, "leftEvent": left_event }) - //print("room up", rooms.toJson()) } @@ -65,19 +64,35 @@ function onTimelineEventReceived( event_type, room_id, event_id, sender_id, date, content, content_type, is_local_echo, show_name_line, translatable, target_user_id ) { - models.timelines.upsert({"eventId": event_id}, { + var item = { "eventType": py.getattr(event_type, "__name__"), "roomId": room_id, "eventId": event_id, "senderId": sender_id, "date": date, "content": content, - "contentType": content, - "isLocalEcho": is_local_echo, + "contentType": content_type, "showNameLine": show_name_line, "translatable": translatable, - "targetUserId": target_user_id - }, true, 1000) + "targetUserId": target_user_id, + "isLocalEcho": is_local_echo, + } + + // Replace any matching local echo + var found = models.timelines.getIndices({ + "roomId": room_id, + "senderId": sender_id, + "content": content, + "isLocalEcho": true + }, 1, 500) + if (found.length > 0) { + models.timelines.set(found[0], item) + return + } + + // Multiple clients will emit duplicate events with the same eventId + models.timelines.upsert({"eventId": event_id}, item, true, 500) } + var onTimelineMessageReceived = onTimelineEventReceived