Organize banners, add LeftBanner

Previously there was InviteOffer, now there's a base Banner component,
InviteBanner and LeftBanner.
This commit is contained in:
miruka 2019-04-21 15:20:20 -04:00
parent 909e1c3363
commit ea8f75c729
14 changed files with 274 additions and 134 deletions

View File

@ -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

View File

@ -22,15 +22,16 @@ _POOLS: DefaultDict[str, ThreadPoolExecutor] = \
class Client(QObject):
roomInvited = pyqtSignal(str)
roomInvited = pyqtSignal(str, dict)
roomJoined = pyqtSignal(str)
roomLeft = pyqtSignal(str)
roomInvited = pyqtSignal([str, dict], [str])
roomJoined = 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)
messageAboutToBeSent = pyqtSignal(str, dict)
def __init__(self,
@ -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()

View File

@ -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):

View File

@ -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)

View File

@ -13,7 +13,8 @@ from .backend import Backend
from .client import Client
from .model.items import Room, RoomEvent, User
Inviter = Optional[Dict[str, str]]
Inviter = Optional[Dict[str, str]]
LeftEvent = Optional[Dict[str, str]]
class SignalManager(QObject):
@ -23,8 +24,8 @@ class SignalManager(QObject):
super().__init__(parent=backend)
self.backend = backend
self.last_room_events: Deque[str] = Deque(maxlen=1000)
self._events_in_transfer: int = 0
self.last_room_events: Deque[str] = Deque(maxlen=1000)
self._events_in_transfer: int = 0
cm = self.backend.clientManager
cm.clientAdded.connect(self.onClientAdded)
@ -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: MatrixRoom,
category: str,
inviter: Inviter = None) -> None:
model = self.backend.models.rooms[client.userId]
client: Client,
room_id: str,
room: MatrixRoom,
category: str,
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(

View File

@ -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"
)
}

View 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
}
}
}
}
}

View 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),
}
]
}

View File

@ -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
}
}
}
}

View 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),
}
]
}

View File

@ -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
}
}

View File

@ -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 = []

View 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

View 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