Implement a device management page

This commit is contained in:
Tobias Fella
2020-12-10 13:33:07 +01:00
parent d6e56174b5
commit 8e2cdc8f08
9 changed files with 253 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
/**
* SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
*
* SPDX-LicenseIdentifier: GPL-2.0-or-later
*/
import QtQuick 2.14
import QtQuick.Controls 2.14 as Controls
import QtQuick.Layouts 1.14
import org.kde.kirigami 2.12 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
title: i18n("Devices")
ListView {
model: DevicesModel {
id: devices
}
delegate: Kirigami.SwipeListItem {
leftPadding: 0
rightPadding: 0
Kirigami.BasicListItem {
anchors.top: parent.top
anchors.bottom: parent.bottom
text: model.displayName
subtitle: model.id
icon: "network-connect"
}
actions: [
Kirigami.Action {
text: i18n("Edit device name")
iconName: "document-edit"
onTriggered: {
renameSheet.index = model.index
renameSheet.name = model.displayName
renameSheet.open()
}
},
Kirigami.Action {
text: i18n("Logout device")
iconName: "edit-delete-remove"
onTriggered: {
passwordSheet.index = index
passwordSheet.open()
}
}
]
}
}
Kirigami.OverlaySheet {
id: passwordSheet
property var index
header: Kirigami.Heading {
text: i18n("Remove device")
}
Kirigami.FormLayout {
Controls.TextField {
id: passwordField
Kirigami.FormData.label: i18n("Password:")
echoMode: TextInput.Password
}
Controls.Button {
text: i18n("Confirm")
onClicked: {
devices.logout(passwordSheet.index, passwordField.text)
passwordField.text = ""
passwordSheet.close()
}
}
}
}
Kirigami.OverlaySheet {
id: renameSheet
property int index
property string name
header: Kirigami.Heading {
text: i18n("Edit device")
}
Kirigami.FormLayout {
Controls.TextField {
id: nameField
Kirigami.FormData.label: i18n("Name:")
text: renameSheet.name
}
Controls.Button {
text: i18n("Save")
onClicked: {
devices.setName(renameSheet.index, nameField.text)
renameSheet.close()
}
}
}
}
}

View File

@@ -160,6 +160,12 @@ Kirigami.ApplicationWindow {
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/AccountsPage.qml")
enabled: pageStack.layers.currentItem.title !== i18n("Accounts")
},
Kirigami.Action {
text: i18n("Devices")
iconName: "network-connect"
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/DevicesPage.qml")
enabled: pageStack.layers.currentItem.title !== i18n("Devices")
},
Kirigami.Action {
text: i18n("Settings")
icon.name: "settings-configure"

View File

@@ -13,6 +13,7 @@
<file>imports/NeoChat/Page/InvitationPage.qml</file>
<file>imports/NeoChat/Page/StartChatPage.qml</file>
<file>imports/NeoChat/Page/ImageEditorPage.qml</file>
<file>imports/NeoChat/Page/DevicesPage.qml</file>
<file>imports/NeoChat/Component/qmldir</file>
<file>imports/NeoChat/Component/ChatTextInput.qml</file>
<file>imports/NeoChat/Component/AutoMouseArea.qml</file>

View File

@@ -16,6 +16,7 @@ add_executable(neochat
notificationsmanager.cpp
sortfilterroomlistmodel.cpp
chatdocumenthandler.cpp
devicesmodel.cpp
../res.qrc
)

View File

@@ -563,3 +563,12 @@ QList<QKeySequence> Controller::preferencesShortcuts() const
{
return KStandardShortcut::preferences();
}
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString& deviceId, const Omittable<QJsonObject> &auth)
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"),
QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId))
{
QJsonObject _data;
addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
setRequestData(std::move(_data));
}

View File

@@ -130,4 +130,9 @@ public:
explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth = none);
};
class NeochatDeleteDeviceJob : public BaseJob {
public:
explicit NeochatDeleteDeviceJob(const QString& deviceId, const Omittable<QJsonObject> &auth = none);
};
#endif // CONTROLLER_H

87
src/devicesmodel.cpp Normal file
View File

@@ -0,0 +1,87 @@
/**
* SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
*
* SPDX-LicenseIdentifier: GPL-2.0-or-later
*/
#include "devicesmodel.h"
#include <csapi/device_management.h>
#include "controller.h"
DevicesModel::DevicesModel(QObject *parent)
: QAbstractListModel(parent)
{
GetDevicesJob *job = Controller::instance().activeConnection()->callApi<GetDevicesJob>();
connect(job, &BaseJob::success, this, [this, job]() {
beginResetModel();
m_devices = job->devices();
endResetModel();
});
}
QVariant DevicesModel::data(const QModelIndex& index, int role) const
{
if(index.row() < 0 || index.row() >= rowCount(QModelIndex()))
return QVariant();
switch(role) {
case Id:
return m_devices[index.row()].deviceId;
case DisplayName:
return m_devices[index.row()].displayName;
case LastIp:
return m_devices[index.row()].lastSeenIp;
case LastTimestamp:
if(m_devices[index.row()].lastSeenTs)
return *m_devices[index.row()].lastSeenTs;
}
return QVariant();
}
int DevicesModel::rowCount(const QModelIndex& parent) const
{
return m_devices.size();
}
QHash<int, QByteArray> DevicesModel::roleNames() const
{
return {{Id, "id"}, {DisplayName, "displayName"}, {LastIp, "lastIp"}, {LastTimestamp, "lastTimestamp"}};
}
void DevicesModel::logout(int index, const QString &password)
{
auto job = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
connect(job, &BaseJob::result, this, [this, job, password, index] {
if (job->error() != 0) {
QJsonObject replyData = job->jsonData();
QJsonObject authData;
authData["session"] = replyData["session"];
authData["password"] = password;
authData["type"] = "m.login.password";
QJsonObject identifier = {{"type", "m.id.user"}, {"user", Controller::instance().activeConnection()->user()->id()}};
authData["identifier"] = identifier;
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
connect(innerJob, &BaseJob::success, this, [this, index]() {
Q_EMIT beginRemoveRows(QModelIndex(), index, index);
m_devices.remove(index);
Q_EMIT endRemoveRows();
});
}
});
}
void DevicesModel::setName(int index, const QString &name)
{
auto job = Controller::instance().activeConnection()->callApi<UpdateDeviceJob>(m_devices[index].deviceId, name);
QString oldName = m_devices[index].displayName;
Q_EMIT beginResetModel();
m_devices[index].displayName = name;
Q_EMIT endResetModel();
connect(job, &BaseJob::failure, this, [=]() {
Q_EMIT beginResetModel();
m_devices[index].displayName = oldName;
Q_EMIT endResetModel();
});
}

39
src/devicesmodel.h Normal file
View File

@@ -0,0 +1,39 @@
/**
* SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
*
* SPDX-LicenseIdentifier: GPL-2.0-or-later
*/
#pragma once
#include <QAbstractListModel>
#include <csapi/definitions/client_device.h>
using namespace Quotient;
class DevicesModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
Id,
DisplayName,
LastIp,
LastTimestamp,
};
Q_ENUM(Roles);
DevicesModel(QObject *parent = nullptr);
QVariant data(const QModelIndex & index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex & parent) const override;
Q_INVOKABLE void logout(int index, const QString &password);
Q_INVOKABLE void setName(int index, const QString &name);
private:
QVector<Quotient::Device> m_devices;
};

View File

@@ -41,6 +41,7 @@
#include "sortfilterroomlistmodel.h"
#include "userdirectorylistmodel.h"
#include "userlistmodel.h"
#include "devicesmodel.h"
using namespace Quotient;
@@ -89,6 +90,7 @@ int main(int argc, char *argv[])
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
qmlRegisterType<EmojiModel>("org.kde.neochat", 1, 0, "EmojiModel");
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<RoomType>("org.kde.neochat", 1, 0, "RoomType", "ENUM");
qmlRegisterUncreatableType<UserType>("org.kde.neochat", 1, 0, "UserType", "ENUM");