Organize banners, add LeftBanner
Previously there was InviteOffer, now there's a base Banner component, InviteBanner and LeftBanner.
This commit is contained in:
parent
909e1c3363
commit
ea8f75c729
2
TODO.md
2
TODO.md
|
@ -13,6 +13,7 @@
|
|||
- Graphic bug when resizing window vertically for side pane?
|
||||
- Fix tooltip hide()
|
||||
- ![A picture](https://picsum.photos/256/256) not clickable?
|
||||
- Icons aren't reloaded
|
||||
|
||||
- UI
|
||||
- Use HRowLayout and its totalSpacing wherever possible
|
||||
|
@ -48,6 +49,7 @@
|
|||
it should be the peer's display name instead.
|
||||
|
||||
- Missing nio support
|
||||
- Left room events
|
||||
- `org.matrix.room.preview_urls` event
|
||||
- `m.room.aliases` event
|
||||
- Avatars
|
||||
|
|
|
@ -22,14 +22,15 @@ _POOLS: DefaultDict[str, ThreadPoolExecutor] = \
|
|||
|
||||
|
||||
class Client(QObject):
|
||||
roomInvited = pyqtSignal(str)
|
||||
roomInvited = pyqtSignal(str, dict)
|
||||
roomInvited = pyqtSignal([str, dict], [str])
|
||||
roomJoined = pyqtSignal(str)
|
||||
roomLeft = pyqtSignal(str)
|
||||
roomLeft = pyqtSignal([str, dict], [str])
|
||||
|
||||
roomSyncPrevBatchTokenReceived = pyqtSignal(str, str)
|
||||
roomPastPrevBatchTokenReceived = pyqtSignal(str, str)
|
||||
roomEventReceived = pyqtSignal(str, str, dict)
|
||||
roomTypingUsersUpdated = pyqtSignal(str, list)
|
||||
|
||||
messageAboutToBeSent = pyqtSignal(str, dict)
|
||||
|
||||
|
||||
|
@ -120,13 +121,13 @@ class Client(QObject):
|
|||
|
||||
for room_id, room_info in response.rooms.invite.items():
|
||||
for ev in room_info.invite_state:
|
||||
member_event = isinstance(ev, ne.InviteMemberEvent)
|
||||
member_ev = isinstance(ev, ne.InviteMemberEvent)
|
||||
|
||||
if member_event and ev.content["membership"] == "join":
|
||||
if member_ev and ev.content["membership"] == "join":
|
||||
self.roomInvited.emit(room_id, ev.content)
|
||||
break
|
||||
else:
|
||||
self.roomInvited.emit(room_id)
|
||||
self.roomInvited[str].emit(room_id)
|
||||
|
||||
for room_id, room_info in response.rooms.join.items():
|
||||
self.roomJoined.emit(room_id)
|
||||
|
@ -146,8 +147,15 @@ class Client(QObject):
|
|||
else:
|
||||
print("ephemeral event: ", ev)
|
||||
|
||||
for room_id in response.rooms.leave:
|
||||
self.roomLeft.emit(room_id)
|
||||
for room_id, room_info in response.rooms.leave.items():
|
||||
for ev in room_info.timeline.events:
|
||||
member_ev = isinstance(ev, ne.RoomMemberEvent)
|
||||
|
||||
if member_ev and ev.content["membership"] in ("leave", "ban"):
|
||||
self.roomLeft.emit(room_id, ev.__dict__)
|
||||
break
|
||||
else:
|
||||
self.roomLeft[str].emit(room_id)
|
||||
|
||||
|
||||
@futurize()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Any, Callable, Optional, Tuple, Union
|
||||
from typing import Any, Callable, Optional, Sequence, Tuple, Union
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||
|
||||
|
@ -6,8 +6,9 @@ from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
|||
class ListItem(QObject):
|
||||
roles: Tuple[str, ...] = ()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, *args, no_update: Sequence[str] = (), **kwargs):
|
||||
super().__init__()
|
||||
self.no_update = no_update
|
||||
|
||||
for role, value in zip(self.roles, args):
|
||||
setattr(self, role, value)
|
||||
|
@ -17,8 +18,9 @@ class ListItem(QObject):
|
|||
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "%s(%s)" % (
|
||||
return "%s(no_update=%s, %s)" % (
|
||||
type(self).__name__,
|
||||
self.no_update,
|
||||
", ".join((f"{r}={getattr(self, r)!r}" for r in self.roles)),
|
||||
)
|
||||
|
||||
|
@ -63,7 +65,7 @@ class User(ListItem):
|
|||
|
||||
class Room(ListItem):
|
||||
roles = ("roomId", "category", "displayName", "topic", "typingUsers",
|
||||
"inviter")
|
||||
"inviter", "leftEvent")
|
||||
|
||||
categoryChanged = pyqtSignal(str)
|
||||
displayNameChanged = pyqtSignal("QVariant")
|
||||
|
@ -75,7 +77,8 @@ class Room(ListItem):
|
|||
displayName = prop(str, "displayName", displayNameChanged)
|
||||
topic = prop(str, "topic", topicChanged, "")
|
||||
typingUsers = prop(list, "typingUsers", typingUsersChanged, [])
|
||||
inviter = prop("QVariantMap", "inviter")
|
||||
inviter = prop("QVariant", "inviter")
|
||||
leftEvent = prop("QVariant", "leftEvent")
|
||||
|
||||
|
||||
class RoomEvent(ListItem):
|
||||
|
|
|
@ -148,6 +148,9 @@ class ListModel(QAbstractListModel):
|
|||
value = self._convert_new_value(value)
|
||||
|
||||
for role in self.roles:
|
||||
if role in value.no_update:
|
||||
continue
|
||||
|
||||
setattr(self._data[index], role, getattr(value, role))
|
||||
|
||||
qidx = QAbstractListModel.index(self, index, 0)
|
||||
|
|
|
@ -14,6 +14,7 @@ from .client import Client
|
|||
from .model.items import Room, RoomEvent, User
|
||||
|
||||
Inviter = Optional[Dict[str, str]]
|
||||
LeftEvent = Optional[Dict[str, str]]
|
||||
|
||||
|
||||
class SignalManager(QObject):
|
||||
|
@ -60,45 +61,60 @@ class SignalManager(QObject):
|
|||
client: Client,
|
||||
room_id: str,
|
||||
inviter: Inviter = None) -> None:
|
||||
self._add_room(
|
||||
client, client.nio.invited_rooms[room_id], "Invites", inviter
|
||||
)
|
||||
|
||||
self._add_room(client, room_id, client.nio.invited_rooms[room_id],
|
||||
"Invites", inviter=inviter)
|
||||
|
||||
|
||||
def onRoomJoined(self, client: Client, room_id: str) -> None:
|
||||
self._add_room(client, client.nio.rooms[room_id], "Rooms")
|
||||
self._add_room(client, room_id, client.nio.rooms[room_id], "Rooms")
|
||||
|
||||
|
||||
def onRoomLeft(self,
|
||||
client: Client,
|
||||
room_id: str,
|
||||
left_event: LeftEvent = None) -> None:
|
||||
|
||||
self._add_room(client, room_id, client.nio.rooms.get(room_id), "Left",
|
||||
left_event=left_event)
|
||||
|
||||
|
||||
def _add_room(self,
|
||||
client: Client,
|
||||
room_id: str,
|
||||
room: MatrixRoom,
|
||||
category: str,
|
||||
inviter: Inviter = None) -> None:
|
||||
model = self.backend.models.rooms[client.userId]
|
||||
inviter: Inviter = None,
|
||||
left_event: LeftEvent = None) -> None:
|
||||
|
||||
assert not (inviter and left_event)
|
||||
|
||||
model = self.backend.models.rooms[client.userId]
|
||||
no_update = []
|
||||
|
||||
def get_displayname() -> Optional[str]:
|
||||
if not room:
|
||||
no_update.append("displayName")
|
||||
return room_id
|
||||
|
||||
name = room.name or room.canonical_alias
|
||||
if name:
|
||||
return name
|
||||
|
||||
def group_name() -> Optional[str]:
|
||||
name = room.group_name()
|
||||
return None if name == "Empty room?" else name
|
||||
|
||||
item = Room(
|
||||
roomId = room.room_id,
|
||||
roomId = room_id,
|
||||
category = category,
|
||||
displayName = room.name or room.canonical_alias or group_name(),
|
||||
topic = room.topic,
|
||||
displayName = get_displayname(),
|
||||
topic = room.topic if room else "",
|
||||
inviter = inviter,
|
||||
leftEvent = left_event,
|
||||
no_update = no_update,
|
||||
)
|
||||
|
||||
model.updateOrAppendWhere("roomId", room.room_id, item)
|
||||
|
||||
|
||||
def onRoomLeft(self, client: Client, room_id: str) -> None:
|
||||
rooms = self.backend.models.rooms[client.userId]
|
||||
try:
|
||||
index = rooms.indexWhere("roomId", room_id)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
del rooms[index]
|
||||
model.updateOrAppendWhere("roomId", room_id, item)
|
||||
|
||||
|
||||
def onRoomSyncPrevBatchTokenReceived(
|
||||
|
|
|
@ -15,7 +15,7 @@ Controls1.SplitView {
|
|||
}
|
||||
|
||||
StackView {
|
||||
function showRoom(userId, roomId, isInvite) {
|
||||
function showRoom(userId, roomId) {
|
||||
pageStack.replace(
|
||||
"chat/Root.qml", { userId: userId, roomId: roomId }
|
||||
)
|
||||
|
@ -29,6 +29,7 @@ Controls1.SplitView {
|
|||
initialItem: Item { // TODO: (test, remove)
|
||||
Keys.onPressed: pageStack.showRoom(
|
||||
"@test_mary:matrix.org", "!TSXGsbBbdwsdylIOJZ:matrix.org"
|
||||
//"@test_mary:matrix.org", "!TEXkdeErtVCMqClNfb:matrix.org"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
89
harmonyqml/components/chat/Banner.qml
Normal file
89
harmonyqml/components/chat/Banner.qml
Normal file
|
@ -0,0 +1,89 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.4
|
||||
import "../base" as Base
|
||||
|
||||
Rectangle {
|
||||
id: banner
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 32
|
||||
color: "#BBB"
|
||||
|
||||
property alias avatarName: bannerAvatar.name
|
||||
property alias avatarSource: bannerAvatar.imageSource
|
||||
property alias labelText: bannerLabel.text
|
||||
property alias buttonModel: bannerRepeater.model
|
||||
|
||||
Base.HRowLayout {
|
||||
id: bannerRow
|
||||
anchors.fill: parent
|
||||
|
||||
Base.Avatar {
|
||||
id: bannerAvatar
|
||||
dimmension: banner.Layout.preferredHeight
|
||||
}
|
||||
|
||||
Base.HLabel {
|
||||
id: bannerLabel
|
||||
textFormat: Text.StyledText
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
|
||||
visible:
|
||||
bannerRow.width - bannerAvatar.width - bannerButtons.width > 30
|
||||
|
||||
Layout.maximumWidth:
|
||||
bannerRow.width -
|
||||
bannerAvatar.width - bannerButtons.width -
|
||||
Layout.leftMargin - Layout.rightMargin
|
||||
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: Layout.leftMargin
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Base.HRowLayout {
|
||||
id: bannerButtons
|
||||
spacing: 0
|
||||
|
||||
function getButtonsWidth() {
|
||||
var total = 0
|
||||
|
||||
for (var i = 0; i < bannerRepeater.count; i++) {
|
||||
total += bannerRepeater.itemAt(i).implicitWidth
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
property bool compact:
|
||||
bannerRow.width <
|
||||
bannerAvatar.width +
|
||||
bannerLabel.implicitWidth +
|
||||
bannerLabel.Layout.leftMargin +
|
||||
bannerLabel.Layout.rightMargin +
|
||||
getButtonsWidth()
|
||||
|
||||
property int displayMode:
|
||||
compact ? Button.IconOnly : Button.TextBesideIcon
|
||||
|
||||
Repeater {
|
||||
id: bannerRepeater
|
||||
model: []
|
||||
|
||||
Base.HButton {
|
||||
id: declineButton
|
||||
text: modelData.text
|
||||
iconName: modelData.iconName
|
||||
icon.color: modelData.iconColor
|
||||
icon.width: 32
|
||||
display: bannerButtons.displayMode
|
||||
|
||||
Layout.maximumWidth: bannerButtons.compact ? height : -1
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
harmonyqml/components/chat/InviteBanner.qml
Normal file
29
harmonyqml/components/chat/InviteBanner.qml
Normal file
|
@ -0,0 +1,29 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.4
|
||||
import "../base" as Base
|
||||
|
||||
Banner {
|
||||
property var inviter: null
|
||||
|
||||
avatarName: inviter ? inviter.displayname : ""
|
||||
//avatarSource: inviter ? inviter.avatar_url : ""
|
||||
|
||||
labelText:
|
||||
(inviter ?
|
||||
("<b>" + inviter.displayname + "</b>") : qsTr("Someone")) +
|
||||
" " + qsTr("invited you to join the room.")
|
||||
|
||||
buttonModel: [
|
||||
{
|
||||
text: "Accept",
|
||||
iconName: "accept",
|
||||
iconColor: Qt.hsla(0.45, 0.9, 0.3, 1),
|
||||
},
|
||||
{
|
||||
text: "Decline",
|
||||
iconName: "decline",
|
||||
iconColor: Qt.hsla(0.95, 0.9, 0.35, 1),
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.4
|
||||
import "../base" as Base
|
||||
|
||||
Rectangle {
|
||||
id: inviteOffer
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 32
|
||||
color: "#BBB"
|
||||
|
||||
property var inviter: null
|
||||
|
||||
Base.HRowLayout {
|
||||
id: inviteRow
|
||||
anchors.fill: parent
|
||||
|
||||
Base.Avatar {
|
||||
id: inviteAvatar
|
||||
name: inviter ? inviter.displayname : ""
|
||||
dimmension: inviteOffer.Layout.preferredHeight
|
||||
//imageSource: inviter ? inviter.avatar_url : ""
|
||||
}
|
||||
|
||||
Base.HLabel {
|
||||
id: inviteLabel
|
||||
text: (inviter ?
|
||||
("<b>" + inviter.displayname + "</b>") : qsTr("Someone")) +
|
||||
" " + qsTr("invited you to join the room.")
|
||||
textFormat: Text.StyledText
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
|
||||
visible:
|
||||
inviteRow.width - inviteAvatar.width - inviteButtons.width > 30
|
||||
|
||||
Layout.maximumWidth:
|
||||
inviteRow.width -
|
||||
inviteAvatar.width - inviteButtons.width -
|
||||
Layout.leftMargin - Layout.rightMargin
|
||||
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: Layout.leftMargin
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Base.HRowLayout {
|
||||
id: inviteButtons
|
||||
spacing: 0
|
||||
|
||||
property bool compact:
|
||||
inviteRow.width <
|
||||
inviteAvatar.width + inviteLabel.implicitWidth +
|
||||
acceptButton.implicitWidth + declineButton.implicitWidth
|
||||
|
||||
property int displayMode:
|
||||
compact ? Button.IconOnly : Button.TextBesideIcon
|
||||
|
||||
Base.HButton {
|
||||
id: acceptButton
|
||||
text: qsTr("Accept")
|
||||
iconName: "accept"
|
||||
icon.color: Qt.hsla(0.45, 0.9, 0.3, 1)
|
||||
display: inviteButtons.displayMode
|
||||
|
||||
Layout.maximumWidth: inviteButtons.compact ? height : -1
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Base.HButton {
|
||||
id: declineButton
|
||||
text: qsTr("Decline")
|
||||
iconName: "decline"
|
||||
icon.color: Qt.hsla(0.95, 0.9, 0.35, 1)
|
||||
icon.width: 32
|
||||
display: inviteButtons.displayMode
|
||||
|
||||
Layout.maximumWidth: inviteButtons.compact ? height : -1
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
harmonyqml/components/chat/LeftBanner.qml
Normal file
25
harmonyqml/components/chat/LeftBanner.qml
Normal file
|
@ -0,0 +1,25 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.4
|
||||
import "../base" as Base
|
||||
import "utils.js" as ChatJS
|
||||
|
||||
Banner {
|
||||
property var leftEvent: null
|
||||
|
||||
avatarName: ChatJS.getLeftBannerAvatarName(leftEvent, chatPage.userId)
|
||||
labelText: ChatJS.getLeftBannerText(leftEvent)
|
||||
|
||||
buttonModel: [
|
||||
{
|
||||
text: "Rejoin",
|
||||
iconName: "join",
|
||||
iconColor: Qt.hsla(0.13, 0.9, 0.35, 1),
|
||||
},
|
||||
{
|
||||
text: "Forget",
|
||||
iconName: "trash_can",
|
||||
iconColor: Qt.hsla(0.95, 0.9, 0.35, 1),
|
||||
}
|
||||
]
|
||||
}
|
|
@ -9,8 +9,6 @@ ColumnLayout {
|
|||
readonly property var roomInfo:
|
||||
Backend.models.rooms.get(userId).getWhere("roomId", roomId)
|
||||
|
||||
property bool isInvite: roomInfo.category === "Invites"
|
||||
|
||||
id: chatPage
|
||||
spacing: 0
|
||||
onFocusChanged: sendBox.setFocus()
|
||||
|
@ -20,19 +18,22 @@ ColumnLayout {
|
|||
topic: roomInfo.topic
|
||||
}
|
||||
|
||||
|
||||
MessageList {}
|
||||
|
||||
|
||||
TypingUsersBar {}
|
||||
|
||||
InviteOffer {
|
||||
visible: isInvite
|
||||
InviteBanner {
|
||||
visible: roomInfo.category === "Invites"
|
||||
inviter: roomInfo.inviter
|
||||
}
|
||||
|
||||
SendBox {
|
||||
id: sendBox
|
||||
visible: ! isInvite
|
||||
visible: roomInfo.category === "Rooms"
|
||||
}
|
||||
|
||||
LeftBanner {
|
||||
visible: roomInfo.category === "Left"
|
||||
leftEvent: roomInfo.leftEvent
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,6 +151,51 @@ function getMemberEventText(dict) {
|
|||
}
|
||||
|
||||
|
||||
function getLeftBannerText(leftEvent) {
|
||||
if (! leftEvent) {
|
||||
return "You are not member of this room."
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(leftEvent, null, 4))
|
||||
|
||||
var info = leftEvent.content
|
||||
var prev = leftEvent.prev_content
|
||||
var reason = info.reason ? (" Reason: " + info.reason) : ""
|
||||
|
||||
if (leftEvent.state_key === leftEvent.sender) {
|
||||
return (prev && prev.membership === "invite" ?
|
||||
"You declined to join this room." : "You left the room.") +
|
||||
reason
|
||||
}
|
||||
|
||||
if (info.membership)
|
||||
|
||||
var name = Backend.getUserDisplayName(leftEvent.sender, false).result()
|
||||
|
||||
return "<b>" + name + "</b> " +
|
||||
(info.membership == "ban" ?
|
||||
"banned you from the room." :
|
||||
|
||||
prev && prev.membership === "invite" ?
|
||||
"canceled your invitation." :
|
||||
|
||||
prev && prev.membership == "ban" ?
|
||||
"unbanned you from the room." :
|
||||
|
||||
"kicked you out of the room.") +
|
||||
reason
|
||||
}
|
||||
|
||||
|
||||
function getLeftBannerAvatarName(leftEvent, accountId) {
|
||||
if (! leftEvent || leftEvent.state_key == leftEvent.sender) {
|
||||
return Backend.getUserDisplayName(accountId, false).result()
|
||||
}
|
||||
|
||||
return Backend.getUserDisplayName(leftEvent.sender, false).result()
|
||||
}
|
||||
|
||||
|
||||
function getTypingUsersText(users, ourAccountId) {
|
||||
var names = []
|
||||
|
||||
|
|
1
harmonyqml/icons/join.svg
Normal file
1
harmonyqml/icons/join.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16 9v-4l8 7-8 7v-4h-8v-6h8zm-2 10v-.083c-1.178.685-2.542 1.083-4 1.083-4.411 0-8-3.589-8-8s3.589-8 8-8c1.458 0 2.822.398 4 1.083v-2.245c-1.226-.536-2.577-.838-4-.838-5.522 0-10 4.477-10 10s4.478 10 10 10c1.423 0 2.774-.302 4-.838v-2.162z"/></svg>
|
After Width: | Height: | Size: 339 B |
1
harmonyqml/icons/trash_can.svg
Normal file
1
harmonyqml/icons/trash_can.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M3 6v18h18v-18h-18zm5 14c0 .552-.448 1-1 1s-1-.448-1-1v-10c0-.552.448-1 1-1s1 .448 1 1v10zm5 0c0 .552-.448 1-1 1s-1-.448-1-1v-10c0-.552.448-1 1-1s1 .448 1 1v10zm5 0c0 .552-.448 1-1 1s-1-.448-1-1v-10c0-.552.448-1 1-1s1 .448 1 1v10zm4-18v2h-20v-2h5.711c.9 0 1.631-1.099 1.631-2h5.315c0 .901.73 2 1.631 2h5.712z"/></svg>
|
After Width: | Height: | Size: 409 B |
Loading…
Reference in New Issue
Block a user