Implement a device management page
This commit is contained in:
103
imports/NeoChat/Page/DevicesPage.qml
Normal file
103
imports/NeoChat/Page/DevicesPage.qml
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -160,6 +160,12 @@ Kirigami.ApplicationWindow {
|
|||||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/AccountsPage.qml")
|
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/AccountsPage.qml")
|
||||||
enabled: pageStack.layers.currentItem.title !== i18n("Accounts")
|
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 {
|
Kirigami.Action {
|
||||||
text: i18n("Settings")
|
text: i18n("Settings")
|
||||||
icon.name: "settings-configure"
|
icon.name: "settings-configure"
|
||||||
|
|||||||
1
res.qrc
1
res.qrc
@@ -13,6 +13,7 @@
|
|||||||
<file>imports/NeoChat/Page/InvitationPage.qml</file>
|
<file>imports/NeoChat/Page/InvitationPage.qml</file>
|
||||||
<file>imports/NeoChat/Page/StartChatPage.qml</file>
|
<file>imports/NeoChat/Page/StartChatPage.qml</file>
|
||||||
<file>imports/NeoChat/Page/ImageEditorPage.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/qmldir</file>
|
||||||
<file>imports/NeoChat/Component/ChatTextInput.qml</file>
|
<file>imports/NeoChat/Component/ChatTextInput.qml</file>
|
||||||
<file>imports/NeoChat/Component/AutoMouseArea.qml</file>
|
<file>imports/NeoChat/Component/AutoMouseArea.qml</file>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ add_executable(neochat
|
|||||||
notificationsmanager.cpp
|
notificationsmanager.cpp
|
||||||
sortfilterroomlistmodel.cpp
|
sortfilterroomlistmodel.cpp
|
||||||
chatdocumenthandler.cpp
|
chatdocumenthandler.cpp
|
||||||
|
devicesmodel.cpp
|
||||||
../res.qrc
|
../res.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -563,3 +563,12 @@ QList<QKeySequence> Controller::preferencesShortcuts() const
|
|||||||
{
|
{
|
||||||
return KStandardShortcut::preferences();
|
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));
|
||||||
|
}
|
||||||
|
|||||||
@@ -130,4 +130,9 @@ public:
|
|||||||
explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth = none);
|
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
|
#endif // CONTROLLER_H
|
||||||
|
|||||||
87
src/devicesmodel.cpp
Normal file
87
src/devicesmodel.cpp
Normal 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
39
src/devicesmodel.h
Normal 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;
|
||||||
|
};
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
#include "sortfilterroomlistmodel.h"
|
#include "sortfilterroomlistmodel.h"
|
||||||
#include "userdirectorylistmodel.h"
|
#include "userdirectorylistmodel.h"
|
||||||
#include "userlistmodel.h"
|
#include "userlistmodel.h"
|
||||||
|
#include "devicesmodel.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
@@ -89,6 +90,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
|
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
|
||||||
qmlRegisterType<EmojiModel>("org.kde.neochat", 1, 0, "EmojiModel");
|
qmlRegisterType<EmojiModel>("org.kde.neochat", 1, 0, "EmojiModel");
|
||||||
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
|
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<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||||
qmlRegisterUncreatableType<RoomType>("org.kde.neochat", 1, 0, "RoomType", "ENUM");
|
qmlRegisterUncreatableType<RoomType>("org.kde.neochat", 1, 0, "RoomType", "ENUM");
|
||||||
qmlRegisterUncreatableType<UserType>("org.kde.neochat", 1, 0, "UserType", "ENUM");
|
qmlRegisterUncreatableType<UserType>("org.kde.neochat", 1, 0, "UserType", "ENUM");
|
||||||
|
|||||||
Reference in New Issue
Block a user