diff --git a/TODO.md b/TODO.md
index c633866a..b2338fef 100644
--- a/TODO.md
+++ b/TODO.md
@@ -36,3 +36,6 @@
- nio: org.matrix.room.preview\_urls, m.room.aliases
- Markdown: don't turn #things into title (space), disable __ syntax
+- ![A picture](https://picsum.photos/256/256) not clickable?
+
+- On sync, check messages API, if a limited sync timeline was received
diff --git a/harmonyqml/backend/client.py b/harmonyqml/backend/client.py
index 39665d27..6ceb38a2 100644
--- a/harmonyqml/backend/client.py
+++ b/harmonyqml/backend/client.py
@@ -46,6 +46,7 @@ class Client(QObject):
roomPastPrevBatchTokenReceived = pyqtSignal(str, str)
roomEventReceived = pyqtSignal(str, str, dict)
roomTypingUsersUpdated = pyqtSignal(str, list)
+ messageAboutToBeSent = pyqtSignal(str, dict)
def __init__(self,
@@ -171,7 +172,6 @@ class Client(QObject):
return
self._loading = True
- print("load", limit)
self._on_past_events(
room_id,
self.net.talk(
@@ -201,4 +201,11 @@ class Client(QObject):
"format": "org.matrix.custom.html",
"msgtype": "m.text",
}
- self.net.talk(self.nio.room_send, room_id, "m.room.message", content)
+ self.messageAboutToBeSent.emit(room_id, content)
+
+ self.net.talk(
+ self.nio.room_send,
+ room_id = room_id,
+ message_type = "m.room.message",
+ content = content,
+ )
diff --git a/harmonyqml/backend/model/items.py b/harmonyqml/backend/model/items.py
index e785f504..37134d9c 100644
--- a/harmonyqml/backend/model/items.py
+++ b/harmonyqml/backend/model/items.py
@@ -21,6 +21,7 @@ class Room(NamedTuple):
class RoomEvent(NamedTuple):
- type: str
- date_time: QDateTime
- dict: Dict[str, str]
+ type: str
+ date_time: QDateTime
+ dict: Dict[str, str]
+ is_local_echo: bool = False
diff --git a/harmonyqml/backend/signal_manager.py b/harmonyqml/backend/signal_manager.py
index 54c44ab6..3aeb24ec 100644
--- a/harmonyqml/backend/signal_manager.py
+++ b/harmonyqml/backend/signal_manager.py
@@ -6,19 +6,22 @@ from typing import Any, Deque, Dict, List, Optional
from PyQt5.QtCore import QDateTime, QObject, pyqtBoundSignal
+import nio
+
from .backend import Backend
from .client import Client
from .model.items import Room, RoomEvent, User
class SignalManager(QObject):
- _duplicate_check_lock: Lock = Lock()
+ _event_handling_lock: Lock = Lock()
def __init__(self, backend: Backend) -> None:
super().__init__(parent=backend)
self.backend = backend
self.last_room_events: Deque[str] = Deque(maxlen=1000)
+ self._events_in_transfer: int = 0
cm = self.backend.clientManager
cm.clientAdded.connect(self.onClientAdded)
@@ -103,28 +106,53 @@ class SignalManager(QObject):
self, _: Client, room_id: str, etype: str, edict: Dict[str, Any]
) -> None:
- # Prevent duplicate events in models due to multiple accounts
- with self._duplicate_check_lock:
+ with self._event_handling_lock:
+ # Prevent duplicate events in models due to multiple accounts
if edict["event_id"] in self.last_room_events:
return
self.last_room_events.appendleft(edict["event_id"])
- model = self.backend.models.roomEvents[room_id]
- date_time = QDateTime.fromMSecsSinceEpoch(edict["server_timestamp"])
- new_event = RoomEvent(type=etype, date_time=date_time, dict=edict)
+ model = self.backend.models.roomEvents[room_id]
+ date_time = QDateTime\
+ .fromMSecsSinceEpoch(edict["server_timestamp"])
+ new_event = RoomEvent(type=etype, date_time=date_time, dict=edict)
- # Model is sorted from newest to oldest message
- insert_at = None
- for i, event in enumerate(model):
- if new_event.date_time > event.date_time:
- insert_at = i
- break
+ if self._events_in_transfer:
+ local_echoes_met: int = 0
+ replace_at: Optional[int] = None
+
+ # Find if any locally echoed event corresponds to new_event
+ for i, event in enumerate(model):
+ if not event.is_local_echo:
+ continue
+
+ sb = (event.dict["sender"], event.dict["body"])
+ new_sb = (new_event.dict["sender"], new_event.dict["body"])
+
+ if sb == new_sb:
+ # The oldest matching local echo shall be replaced
+ replace_at = max(replace_at or 0, i)
+
+ local_echoes_met += 1
+ if local_echoes_met >= self._events_in_transfer:
+ break
+
+ if replace_at is not None:
+ model[replace_at] = new_event
+ self._events_in_transfer -= 1
+ return
+
+ for i, event in enumerate(model):
+ if event.is_local_echo:
+ continue
+
+ # Model is sorted from newest to oldest message
+ if new_event.date_time > event.date_time:
+ model.insert(i, new_event)
+ return
- if insert_at is None:
model.append(new_event)
- else:
- model.insert(insert_at, new_event)
def onRoomTypingUsersUpdated(
@@ -133,3 +161,25 @@ class SignalManager(QObject):
rooms = self.backend.models.rooms[client.userID]
rooms[rooms.indexWhere("room_id", room_id)].typing_users = users
+
+
+ def onMessageAboutToBeSent(
+ self, client: Client, room_id: str, content: Dict[str, str]
+ ) -> None:
+ with self._event_handling_lock:
+ timestamp = QDateTime.currentMSecsSinceEpoch()
+ model = self.backend.models.roomEvents[room_id]
+ nio_event = nio.events.RoomMessage.parse_event({
+ "event_id": "",
+ "sender": client.userID,
+ "origin_server_ts": timestamp,
+ "content": content,
+ })
+ event = RoomEvent(
+ type = type(nio_event).__name__,
+ date_time = QDateTime.fromMSecsSinceEpoch(timestamp),
+ dict = nio_event.__dict__,
+ is_local_echo = True,
+ )
+ model.insert(0, event)
+ self._events_in_transfer += 1
diff --git a/harmonyqml/components/chat/MessageContent.qml b/harmonyqml/components/chat/MessageContent.qml
index 305f9f12..8adad2ef 100644
--- a/harmonyqml/components/chat/MessageContent.qml
+++ b/harmonyqml/components/chat/MessageContent.qml
@@ -37,13 +37,15 @@ Row {
//Qt.formatDateTime(date_time, "hh:mm:ss") +
//"" +
// (isOwn ? " " + content : "")
-
+ //
text: (dict.formatted_body ?
Backend.htmlFilter.filter(dict.formatted_body) :
dict.body) +
" " +
Qt.formatDateTime(date_time, "hh:mm:ss") +
- ""
+ "" +
+ (is_local_echo ?
+ " ⏳" : "")
textFormat: Text.RichText
background: Rectangle {color: "#DDD"}
wrapMode: Text.Wrap