Issue #2 is actually fixed!
A somewhat primitive UI for room management.
A new style for AutoTextField.
Limit max width of drawers.
This commit is contained in:
Black Hat 2019-04-22 19:49:22 +08:00
parent dda8738e8c
commit c727eb3bfd
6 changed files with 309 additions and 234 deletions

View File

@ -1,8 +1,62 @@
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.12 import QtQuick.Controls 2.12
import Spectral.Setting 0.1 import QtQuick.Controls.Material 2.3
TextField { TextField {
id: textField
selectByMouse: true selectByMouse: true
topPadding: 8
bottomPadding: 8
background: Item {
Label {
id: floatingPlaceholder
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: textField.topPadding
anchors.leftMargin: textField.leftPadding
transformOrigin: Item.TopLeft
visible: false
color: Material.accent
states: [
State {
name: "shown"
when: textField.text.length !== 0
PropertyChanges { target: floatingPlaceholder; scale: 0.8 }
PropertyChanges { target: floatingPlaceholder; anchors.topMargin: -floatingPlaceholder.height * 0.4 }
}
]
transitions: [
Transition {
to: "shown"
SequentialAnimation {
PropertyAction { target: floatingPlaceholder; property: "text"; value: textField.placeholderText }
PropertyAction { target: floatingPlaceholder; property: "visible"; value: true }
PropertyAction { target: textField; property: "placeholderTextColor"; value: "transparent" }
ParallelAnimation {
NumberAnimation { target: floatingPlaceholder; property: "scale"; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: floatingPlaceholder; property: "anchors.topMargin"; duration: 250; easing.type: Easing.InOutQuad }
}
}
},
Transition {
from: "shown"
SequentialAnimation {
ParallelAnimation {
NumberAnimation { target: floatingPlaceholder; property: "scale"; duration: 250; easing.type: Easing.InOutQuad }
NumberAnimation { target: floatingPlaceholder; property: "anchors.topMargin"; duration: 250; easing.type: Easing.InOutQuad }
}
PropertyAction { target: textField; property: "placeholderTextColor"; value: "grey" }
PropertyAction { target: floatingPlaceholder; property: "visible"; value: false }
}
}
]
}
}
} }

View File

@ -59,54 +59,62 @@ Drawer {
Layout.fillWidth: true Layout.fillWidth: true
} }
RowLayout { Control {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 8 padding: 0
MaterialIcon { contentItem: RowLayout {
Layout.preferredWidth: 32 spacing: 8
Layout.preferredHeight: 32
Layout.alignment: Qt.AlignTop
icon: "\ue88f" MaterialIcon {
color: MPalette.lighter Layout.preferredWidth: 32
Layout.preferredHeight: 32
Layout.alignment: Qt.AlignTop
icon: "\ue88f"
color: MPalette.lighter
}
ColumnLayout {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: room && room.canonicalAlias ? room.canonicalAlias : "No Canonical Alias"
color: MPalette.accent
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: "Main Alias"
color: MPalette.lighter
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: room && room.topic ? room.topic : "No Topic"
color: MPalette.foreground
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: "Topic"
color: MPalette.lighter
}
}
} }
ColumnLayout { background: RippleEffect {
Layout.fillWidth: true onPrimaryClicked: roomDetailDialog.open()
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: room && room.canonicalAlias ? room.canonicalAlias : "No Canonical Alias"
color: MPalette.accent
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: "Main Alias"
color: MPalette.lighter
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: room && room.topic ? room.topic : "No Topic"
color: MPalette.foreground
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: "Topic"
color: MPalette.lighter
}
} }
} }
@ -209,9 +217,115 @@ Drawer {
contentItem: AutoTextField { contentItem: AutoTextField {
id: inviteUserDialogTextField id: inviteUserDialogTextField
placeholderText: "@bot:matrix.org" placeholderText: "User ID"
} }
onAccepted: room.inviteToRoom(inviteUserDialogTextField.text) onAccepted: room.inviteToRoom(inviteUserDialogTextField.text)
} }
Dialog {
anchors.centerIn: parent
width: 480
id: roomDetailDialog
parent: ApplicationWindow.overlay
title: "Room Settings - " + (room ? room.displayName : "")
modal: true
contentItem: ColumnLayout {
RowLayout {
Layout.fillWidth: true
spacing: 16
Avatar {
Layout.preferredWidth: 72
Layout.preferredHeight: 72
Layout.alignment: Qt.AlignTop
hint: room ? room.displayName : "No name"
source: room ? room.avatarMediaId : null
}
ColumnLayout {
Layout.fillWidth: true
Layout.margins: 4
AutoTextField {
Layout.fillWidth: true
text: room ? room.name : ""
placeholderText: "Room Name"
}
AutoTextField {
Layout.fillWidth: true
text: room ? room.topic : ""
placeholderText: "Room Topic"
}
}
}
MenuSeparator {
Layout.fillWidth: true
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
Label {
Layout.preferredWidth: 100
wrapMode: Label.Wrap
text: "Main Alias"
color: MPalette.lighter
}
ComboBox {
Layout.fillWidth: true
model: room ? room.aliases : null
currentIndex: room ? room.aliases.indexOf(room.canonicalAlias) : -1
}
}
RowLayout {
Layout.fillWidth: true
Label {
Layout.preferredWidth: 100
Layout.alignment: Qt.AlignTop
wrapMode: Label.Wrap
text: "Aliases"
color: MPalette.lighter
}
ColumnLayout {
Layout.fillWidth: true
Repeater {
model: room ? room.aliases : null
delegate: Label {
Layout.fillWidth: true
text: modelData
font.pixelSize: 12
color: MPalette.lighter
}
}
}
}
}
}
}
} }

View File

@ -44,8 +44,8 @@ Item {
switch (category) { switch (category) {
case 1: return "Invited" case 1: return "Invited"
case 2: return "Favorites" case 2: return "Favorites"
case 3: return "Rooms" case 3: return "People"
case 4: return "People" case 4: return "Rooms"
case 5: return "Low Priority" case 5: return "Low Priority"
} }
} }
@ -80,135 +80,6 @@ Item {
] ]
} }
// Drawer {
// width: Math.max(root.width, 400)
// height: root.height
// id: drawer
// edge: Qt.LeftEdge
// ColumnLayout {
// anchors.fill: parent
// id: mainColumn
// spacing: 0
// Control {
// Layout.fillWidth: true
// Layout.preferredHeight: 330
// padding: 24
// contentItem: ColumnLayout {
// spacing: 4
// Avatar {
// Layout.preferredWidth: 200
// Layout.preferredHeight: 200
// Layout.margins: 12
// Layout.alignment: Qt.AlignHCenter
// source: root.user ? root.user.avatarMediaId : null
// hint: root.user ? root.user.displayName : "?"
// }
// Label {
// Layout.alignment: Qt.AlignHCenter
// text: root.user ? root.user.displayName : "No Name"
// color: "white"
// font.pixelSize: 22
// }
// Label {
// Layout.alignment: Qt.AlignHCenter
// text: root.user ? root.user.id : "@example:matrix.org"
// color: "white"
// opacity: 0.7
// font.pixelSize: 13
// }
// }
// background: Rectangle { color: Material.primary }
// RippleEffect {
// anchors.fill: parent
// }
// }
// ScrollView {
// Layout.fillWidth: true
// Layout.fillHeight: true
// clip: true
// ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
// ColumnLayout {
// width: mainColumn.width
// spacing: 0
// Repeater {
// model: AccountListModel {
// controller: spectralController
// }
// delegate: ItemDelegate {
// Layout.fillWidth: true
// text: user.displayName
// onClicked: {
// controller.connection = connection
// drawer.close()
// }
// }
// }
// ItemDelegate {
// Layout.fillWidth: true
// text: "Add Account"
// onClicked: loginDialog.open()
// }
// Rectangle {
// Layout.fillWidth: true
// Layout.preferredHeight: 1
// color: MSettings.darkTheme ? "#424242" : "#e7ebeb"
// }
// ItemDelegate {
// Layout.fillWidth: true
// text: "Settings"
// }
// ItemDelegate {
// Layout.fillWidth: true
// text: "Logout"
// onClicked: controller.logout(controller.connection)
// }
// ItemDelegate {
// Layout.fillWidth: true
// text: "Exit"
// onClicked: Qt.quit()
// }
// }
// }
// }
// }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
@ -225,7 +96,7 @@ Item {
rightPadding: 18 rightPadding: 18
contentItem: RowLayout { contentItem: RowLayout {
ItemDelegate { ToolButton {
Layout.preferredWidth: height Layout.preferredWidth: height
Layout.fillHeight: true Layout.fillHeight: true
@ -275,7 +146,7 @@ Item {
onClicked: filterMenu.popup() onClicked: filterMenu.popup()
} }
ItemDelegate { ToolButton {
Layout.preferredWidth: height Layout.preferredWidth: height
Layout.fillHeight: true Layout.fillHeight: true
@ -290,16 +161,12 @@ Item {
readonly property bool active: text readonly property bool active: text
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.alignment: Qt.AlignVCenter
id: searchField id: searchField
topPadding: 0
bottomPadding: 0
placeholderText: "Search..." placeholderText: "Search..."
color: MPalette.lighter color: MPalette.lighter
background: Item {}
} }
Avatar { Avatar {
@ -520,6 +387,7 @@ Item {
onTriggered: category === RoomType.Favorite ? currentRoom.removeTag("m.favourite") : currentRoom.addTag("m.favourite", 1.0) onTriggered: category === RoomType.Favorite ? currentRoom.removeTag("m.favourite") : currentRoom.addTag("m.favourite", 1.0)
} }
MenuItem { MenuItem {
text: "Deprioritize" text: "Deprioritize"
checkable: true checkable: true
@ -527,14 +395,18 @@ Item {
onTriggered: category === RoomType.Deprioritized ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", 1.0) onTriggered: category === RoomType.Deprioritized ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", 1.0)
} }
MenuSeparator {} MenuSeparator {}
MenuItem { MenuItem {
text: "Mark as Read" text: "Mark as Read"
onTriggered: currentRoom.markAllMessagesAsRead() onTriggered: currentRoom.markAllMessagesAsRead()
} }
MenuItem { MenuItem {
text: "Leave Room" text: "Leave Room"
Material.foreground: Material.Red
onTriggered: currentRoom.forget() onTriggered: currentRoom.forget()
} }

View File

@ -87,9 +87,9 @@ ApplicationWindow {
title: "Login" title: "Login"
contentItem: Column { contentItem: ColumnLayout {
AutoTextField { AutoTextField {
width: parent.width Layout.fillWidth: true
id: serverField id: serverField
@ -98,7 +98,7 @@ ApplicationWindow {
} }
AutoTextField { AutoTextField {
width: parent.width Layout.fillWidth: true
id: usernameField id: usernameField
@ -108,7 +108,7 @@ ApplicationWindow {
} }
AutoTextField { AutoTextField {
width: parent.width Layout.fillWidth: true
id: passwordField id: passwordField
@ -120,14 +120,6 @@ ApplicationWindow {
} }
footer: DialogButtonBox { footer: DialogButtonBox {
Button {
text: "OK"
flat: true
enabled: !loginDialog.busy
onClicked: loginDialog.doLogin()
}
Button { Button {
text: "Cancel" text: "Cancel"
flat: true flat: true
@ -136,6 +128,14 @@ ApplicationWindow {
onClicked: loginDialog.close() onClicked: loginDialog.close()
} }
Button {
text: "OK"
flat: true
enabled: !loginDialog.busy
onClicked: loginDialog.doLogin()
}
ToolTip { ToolTip {
id: loginButtonTooltip id: loginButtonTooltip
@ -442,7 +442,7 @@ ApplicationWindow {
title: "Start a Chat" title: "Start a Chat"
contentItem: ColumnLayout { contentItem: ColumnLayout {
TextField { AutoTextField {
Layout.fillWidth: true Layout.fillWidth: true
id: identifierField id: identifierField
@ -473,7 +473,7 @@ ApplicationWindow {
title: "Create a Room" title: "Create a Room"
contentItem: ColumnLayout { contentItem: ColumnLayout {
TextField { AutoTextField {
Layout.fillWidth: true Layout.fillWidth: true
id: roomNameField id: roomNameField
@ -481,7 +481,7 @@ ApplicationWindow {
placeholderText: "Room Name" placeholderText: "Room Name"
} }
TextField { AutoTextField {
Layout.fillWidth: true Layout.fillWidth: true
id: roomTopicField id: roomTopicField
@ -496,7 +496,7 @@ ApplicationWindow {
} }
Drawer { Drawer {
width: (inPortrait ? 0.67 : 0.3) * window.width width: Math.min((inPortrait ? 0.67 : 0.3) * window.width, 360)
height: window.height height: window.height
modal: inPortrait modal: inPortrait
interactive: inPortrait interactive: inPortrait
@ -531,7 +531,7 @@ ApplicationWindow {
} }
RoomDrawer { RoomDrawer {
width: (inPortrait ? 0.67 : 0.3) * window.width width: Math.min((inPortrait ? 0.67 : 0.3) * window.width, 360)
height: window.height height: window.height
modal: inPortrait modal: inPortrait
interactive: inPortrait interactive: inPortrait

View File

@ -16,8 +16,10 @@ RoomListModel::RoomListModel(QObject* parent) : QAbstractListModel(parent) {}
RoomListModel::~RoomListModel() {} RoomListModel::~RoomListModel() {}
void RoomListModel::setConnection(Connection* connection) { void RoomListModel::setConnection(Connection* connection) {
if (connection == m_connection) return; if (connection == m_connection)
if (m_connection) m_connection->disconnect(this); return;
if (m_connection)
m_connection->disconnect(this);
if (!connection) { if (!connection) {
qDebug() << "Removing current connection..."; qDebug() << "Removing current connection...";
m_connection = nullptr; m_connection = nullptr;
@ -29,7 +31,8 @@ void RoomListModel::setConnection(Connection* connection) {
m_connection = connection; m_connection = connection;
for (SpectralRoom* room : m_rooms) room->disconnect(this); for (SpectralRoom* room : m_rooms)
room->disconnect(this);
connect(connection, &Connection::connected, this, connect(connection, &Connection::connected, this,
&RoomListModel::doResetModel); &RoomListModel::doResetModel);
@ -40,6 +43,13 @@ void RoomListModel::setConnection(Connection* connection) {
connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom); connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom);
connect(connection, &Connection::aboutToDeleteRoom, this, connect(connection, &Connection::aboutToDeleteRoom, this,
&RoomListModel::deleteRoom); &RoomListModel::deleteRoom);
connect(connection, &Connection::directChatsListChanged, this,
[=](Connection::DirectChatsMap additions,
Connection::DirectChatsMap removals) {
for (QString roomID : additions.values() + removals.values())
refresh(static_cast<SpectralRoom*>(connection->room(roomID)),
{CategoryRole});
});
doResetModel(); doResetModel();
} }
@ -47,11 +57,14 @@ void RoomListModel::setConnection(Connection* connection) {
void RoomListModel::doResetModel() { void RoomListModel::doResetModel() {
beginResetModel(); beginResetModel();
m_rooms.clear(); m_rooms.clear();
for (auto r : m_connection->roomMap()) doAddRoom(r); for (auto r : m_connection->roomMap())
doAddRoom(r);
endResetModel(); endResetModel();
} }
SpectralRoom* RoomListModel::roomAt(int row) { return m_rooms.at(row); } SpectralRoom* RoomListModel::roomAt(int row) {
return m_rooms.at(row);
}
void RoomListModel::doAddRoom(Room* r) { void RoomListModel::doAddRoom(Room* r) {
if (auto* room = static_cast<SpectralRoom*>(r)) { if (auto* room = static_cast<SpectralRoom*>(r)) {
@ -77,16 +90,19 @@ void RoomListModel::connectRoomSignals(SpectralRoom* room) {
connect(room, &Room::addedMessages, this, connect(room, &Room::addedMessages, this,
[=] { refresh(room, {LastEventRole}); }); [=] { refresh(room, {LastEventRole}); });
connect(room, &Room::notificationCountChanged, this, [=] { connect(room, &Room::notificationCountChanged, this, [=] {
if (room->notificationCount() == 0) return; if (room->notificationCount() == 0)
if (room->timelineSize() == 0) return; return;
const RoomEvent* lastEvent = room->messageEvents().rbegin()->get(); if (room->timelineSize() == 0)
if (lastEvent->isStateEvent()) return; return;
User* sender = room->user(lastEvent->senderId()); const RoomEvent* lastEvent = room->messageEvents().rbegin()->get();
if (sender == room->localUser()) return; if (lastEvent->isStateEvent())
emit newMessage( return;
room->id(), lastEvent->id(), room->displayName(), User* sender = room->user(lastEvent->senderId());
sender->displayname(), room->eventToString(*lastEvent), if (sender == room->localUser())
room->avatar(128)); return;
emit newMessage(room->id(), lastEvent->id(), room->displayName(),
sender->displayname(), room->eventToString(*lastEvent),
room->avatar(128));
}); });
} }
@ -129,7 +145,8 @@ void RoomListModel::updateRoom(Room* room, Room* prev) {
void RoomListModel::deleteRoom(Room* room) { void RoomListModel::deleteRoom(Room* room) {
qDebug() << "Deleting room" << room->id(); qDebug() << "Deleting room" << room->id();
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room); const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
if (it == m_rooms.end()) return; // Already deleted, nothing to do if (it == m_rooms.end())
return; // Already deleted, nothing to do
qDebug() << "Erasing room" << room->id(); qDebug() << "Erasing room" << room->id();
const int row = it - m_rooms.begin(); const int row = it - m_rooms.begin();
beginRemoveRows(QModelIndex(), row, row); beginRemoveRows(QModelIndex(), row, row);
@ -138,34 +155,49 @@ void RoomListModel::deleteRoom(Room* room) {
} }
int RoomListModel::rowCount(const QModelIndex& parent) const { int RoomListModel::rowCount(const QModelIndex& parent) const {
if (parent.isValid()) return 0; if (parent.isValid())
return 0;
return m_rooms.count(); return m_rooms.count();
} }
QVariant RoomListModel::data(const QModelIndex& index, int role) const { QVariant RoomListModel::data(const QModelIndex& index, int role) const {
if (!index.isValid()) return QVariant(); if (!index.isValid())
return QVariant();
if (index.row() >= m_rooms.count()) { if (index.row() >= m_rooms.count()) {
qDebug() << "UserListModel: something wrong here..."; qDebug() << "UserListModel: something wrong here...";
return QVariant(); return QVariant();
} }
SpectralRoom* room = m_rooms.at(index.row()); SpectralRoom* room = m_rooms.at(index.row());
if (role == NameRole) return room->displayName(); if (role == NameRole)
if (role == AvatarRole) return room->avatarMediaId(); return room->displayName();
if (role == TopicRole) return room->topic(); if (role == AvatarRole)
return room->avatarMediaId();
if (role == TopicRole)
return room->topic();
if (role == CategoryRole) { if (role == CategoryRole) {
if (room->joinState() == JoinState::Invite) return RoomType::Invited; if (room->joinState() == JoinState::Invite)
if (room->isFavourite()) return RoomType::Favorite; return RoomType::Invited;
if (room->isDirectChat()) return RoomType::Direct; if (room->isFavourite())
if (room->isLowPriority()) return RoomType::Deprioritized; return RoomType::Favorite;
if (room->isDirectChat())
return RoomType::Direct;
if (room->isLowPriority())
return RoomType::Deprioritized;
return RoomType::Normal; return RoomType::Normal;
} }
if (role == UnreadCountRole) return room->unreadCount(); if (role == UnreadCountRole)
if (role == NotificationCountRole) return room->notificationCount(); return room->unreadCount();
if (role == HighlightCountRole) return room->highlightCount(); if (role == NotificationCountRole)
if (role == LastEventRole) return room->lastEvent(); return room->notificationCount();
if (role == LastActiveTimeRole) return room->lastActiveTime(); if (role == HighlightCountRole)
if (role == CurrentRoomRole) return QVariant::fromValue(room); return room->highlightCount();
if (role == LastEventRole)
return room->lastEvent();
if (role == LastActiveTimeRole)
return room->lastActiveTime();
if (role == CurrentRoomRole)
return QVariant::fromValue(room);
return QVariant(); return QVariant();
} }

View File

@ -17,8 +17,8 @@ class RoomType : public QObject {
enum Types { enum Types {
Invited = 1, Invited = 1,
Favorite, Favorite,
Normal,
Direct, Direct,
Normal,
Deprioritized, Deprioritized,
}; };
REGISTER_ENUM(Types) REGISTER_ENUM(Types)
@ -75,9 +75,12 @@ class RoomListModel : public QAbstractListModel {
signals: signals:
void connectionChanged(); void connectionChanged();
void roomAdded(SpectralRoom* room); void roomAdded(SpectralRoom* room);
void newMessage(const QString& roomId, const QString& eventId, void newMessage(const QString& roomId,
const QString& roomName, const QString& senderName, const QString& eventId,
const QString& text, const QImage& icon); const QString& roomName,
const QString& senderName,
const QString& text,
const QImage& icon);
}; };
#endif // ROOMLISTMODEL_H #endif // ROOMLISTMODEL_H