Commit 5eb97502 authored by Dan Leinir Turthra Jensen's avatar Dan Leinir Turthra Jensen 🌈
Browse files

Prettify the Accounts KCM and expand KAccounts integration library

Summary:
This patch represents an effort which aims to expand, beautify, and clean the
KAccounts integration code and visuals. This builds on the existing work, by adding
new models and new job classes to the library, and exposing them through
the QML module, which is used in the KCM as seen in the screenshots below.

New functionality in the library:

AccountsModel
- A model listing what accounts are currently created on a user's system
ServicesModel
- A model listing what services (if any) are available in an account
ProvidersModel
- A model listing what providers are available for new accounts
ChangeAccountDisplayNameJob
- A job which allows changing the user visible name of an account
RemoveAccountJob
- A job which removes an account (and all its credentials)

The QML module has been modified to both include the old names of older jobs,
as well as the new, more descriptive names.

As for the KCM, please see the screenshots in the differential revision below
for what features have been added and modified (the above code all supports
the functionality in the KCM)

Committed with history on request

Differential Revision: https://phabricator.kde.org/D27681
parents 0822434f 2e7257d3
# KAccounts Integration
Integration library and QML module for Accounts-SSO and SignOn-SSO
# Introduction
KAccounts Integration provides a way to share accounts data such as login tokens and general
user information (like usernames and such) between various applications.
The KAccounts library is a KDE Frameworks style abstraction layer on top of the Accounts-SSO
and SignOnD libraries, which uses a combination of models and jobs to expose the functionality
of those.
The kaccounts QML plugin exposes that functionality directly to Qt Quick based applications,
and using the classes only requires importing the module like so:
```
import org.kde.kaccounts 1.2 as KAccounts
```
The main functionality in the library can be accessed through the various classes below, and
the accounts manager can be accessed directly through ```KAccounts::accountsManager()```. The
other central classes are:
## Models
* AccountsModel
* ServicesModel
* ProvidersModel
## Jobs
* AccountServiceToggleJob
* ChangeAccountDisplayNameJob
* CreateAccountJob
* RemoveAccountJob
# KDE Control Module
The Online Accounts KCM is the main user-visible point for KAccounts, and can be accessed
either through System Settings, or directly from any system menu which allows launching of
KCMs directly (including KRunner). It is built using the Qt Quick module mentioned above,
and uses Kirigami as its base.
# Provider and Service files
If you plan on creating new providers and services, you will need to register those with
the accounts manager. Two cmake macros are provided to assist you in the creation and
installation of these files, and further assists in translation integration for them:
* kaccounts_add_provider
* kaccounts_add_service
/*************************************************************************************
* Copyright (C) 2015 by Aleix Pol <aleixpol@kde.org> *
* Copyright (C) 2020 by Dan Leinir Turthra Jensen <admin@leinir.dk> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
......@@ -17,12 +18,35 @@
*************************************************************************************/
#include "kaccountsdeclarativeplugin.h"
#include "createaccountjob.h"
#include "accountsmodel.h"
#include "servicesmodel.h"
#include "providersmodel.h"
#include "accountservicetogglejob.h"
#include "changeaccountdisplaynamejob.h"
#include "createaccountjob.h"
#include "removeaccountjob.h"
#include <qqml.h>
void KAccountsDeclarativePlugin::registerTypes(const char* uri)
{
qmlRegisterType<CreateAccountJob>(uri, 1, 0, "CreateAccountJob");
qmlRegisterType<AccountServiceToggleJob>(uri, 1, 1, "AccountServiceToggleJob");
// Version 1.0
// Consider this registration deprecated - use the one named ...Job below instead
qmlRegisterType<CreateAccountJob>( uri, 1, 0, "CreateAccount");
// Version 1.1
// Consider this registration deprecated - use the one named ...Job below instead
qmlRegisterType<AccountServiceToggleJob>( uri, 1, 1, "AccountServiceToggle");
// Version 1.2
qmlRegisterType<AccountsModel>( uri, 1, 2, "AccountsModel");
qmlRegisterType<ProvidersModel>( uri, 1, 2, "ProvidersModel");
qmlRegisterType<ServicesModel>( uri, 1, 2, "ServicesModel");
qmlRegisterType<AccountServiceToggleJob>( uri, 1, 2, "AccountServiceToggleJob");
qmlRegisterType<ChangeAccountDisplayNameJob>( uri, 1, 2, "ChangeAccountDisplayNameJob");
qmlRegisterType<CreateAccountJob>( uri, 1, 2, "CreateAccountJob");
qmlRegisterType<RemoveAccountJob>( uri, 1, 2, "RemoveAccountJob");
}
......@@ -32,7 +32,9 @@ AccountsSettings::AccountsSettings(QObject* parent, const QVariantList& args)
KAboutData* about = new KAboutData(QStringLiteral("kcm_kaccounts"), i18n("Accounts"),
QStringLiteral("1.0"), QString(), KAboutLicense::LGPL);
about->addAuthor(i18n("Sebastian Kügler"), QString(), QStringLiteral("sebas@kde.org"));
about->addAuthor(i18n("Dan Leinir Turthra Jensen"), QString(), QStringLiteral("admin@leinir.dk"), QString(), QStringLiteral("leinir"));
setAboutData(about);
setButtons(KQuickAddons::ConfigModule::NoAdditionalButton);
}
#include "accounts.moc"
/*
* Copyright 2019 Nicolas Fella <nicolas.fella@gmx.de>
* Copyright 2020 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import QtQuick 2.12
import QtQuick.Controls 2.12 as Controls
import QtQuick.Layouts 1.12
import org.kde.kirigami 2.4 as Kirigami
import org.kde.kcm 1.2
import org.kde.kaccounts 1.2 as KAccounts
SimpleKCM {
id: component;
title: i18n("Account Details")
property alias model: servicesList.model
RemoveAccountDialog {
id: accountRemover
parent: component
accountId: servicesList.model.accountId
displayName: servicesList.model.accountDisplayName
providerName: servicesList.model.accountProviderName
onAccountRemoved: kcm.pop()
}
RenameAccountDialog {
id: accountRenamer
parent: component
accountId: servicesList.model.accountId
currentDisplayName: servicesList.model.accountDisplayName
}
Component {
id: serviceToggleJob
KAccounts.AccountServiceToggleJob { }
}
header: RowLayout {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
source: model.accountIconName
Layout.preferredWidth: Kirigami.Units.iconSizes.large
Layout.preferredHeight: Kirigami.Units.iconSizes.large
}
Controls.Label {
Layout.fillWidth: true
text: {
if (model.accountDisplayName.length > 0 && model.accountProviderName.length > 0) {
return i18n("%1 (%2)", model.accountDisplayName, model.accountProviderName)
} else if (model.accountDisplayName.length > 0) {
return model.accountDisplayName
} else {
return i18n("%1 account", model.accountProviderName)
}
}
}
Controls.ToolButton {
icon.name: "edit-entry"
display: Controls.AbstractButton.IconOnly
Layout.preferredHeight: Kirigami.Units.iconSizes.large
Layout.preferredWidth: Kirigami.Units.iconSizes.large
onClicked: accountRenamer.open();
Controls.ToolTip {
visible: parent.hovered && !parent.pressed
text: i18nc("Button which spawns a dialog allowing the user to change the displayed account's human-readable name", "Change Account Display Name")
delay: Kirigami.Units.toolTipDelay
timeout: 5000
y: parent.height
}
}
}
footer: RowLayout {
Controls.Button {
Layout.alignment: Qt.AlignRight
text: i18n("Remove This Account")
icon.name: "edit-delete-remove"
onClicked: accountRemover.open();
}
}
Controls.Frame {
Layout.fillWidth: true
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.Button
border {
width: 1
color: Qt.tint(Kirigami.Theme.textColor, Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.8))
}
}
ColumnLayout {
anchors.fill: parent
spacing: Kirigami.Units.smallSpacing
Controls.Label {
visible: servicesList.count === 0
Layout.fillWidth: true
Layout.minimumHeight: component.height / 3
enabled: false
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
text: i18nc("A text shown when an account has no configurable services", "There are no configurable services available for this account. You can still change its display name by clicking the edit icon above.")
}
Kirigami.Heading {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
visible: servicesList.count > 0
level: 3
text: i18nc("Heading for a list of services available with this account", "Use This Account For")
}
Repeater {
id: servicesList
delegate: ColumnLayout {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
spacing: 0
Controls.CheckBox {
id: serviceCheck
Layout.fillWidth: true
checked: model.enabled
text: model.displayName
Binding {
target: serviceCheck
property: "checked"
value: model.enabled
}
onClicked: {
var job = serviceToggleJob.createObject(component, { "accountId": servicesList.model.accountId, "serviceId": model.name, "serviceEnabled": !model.enabled })
job.start()
}
}
Controls.Label {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing * 2
visible: text.length > 0
text: model.description
wrapMode: Text.Wrap
}
}
}
Item {
Layout.fillWidth: true
height: Kirigami.Units.smallSpacing
}
}
}
}
......@@ -18,48 +18,46 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import QtQuick 2.7
import QtQuick.Layouts 1.11
import QtQuick.Controls 2.0 as Controls
import QtQuick 2.12
import QtQuick.Controls 2.12 as Controls
import QtQuick.Layouts 1.12
import org.kde.kirigami 2.7 as Kirigami
import org.kde.kaccounts 1.0
import org.kde.kcm 1.2
import Ubuntu.OnlineAccounts 0.1 as OA
import org.kde.kaccounts 1.2 as KAccounts
ScrollViewKCM {
id: kaccountsRoot
// Existing accounts
view: ListView {
model: OA.AccountServiceModel {
id: accountsModel
service: "global"
includeDisabled: true
}
model: KAccounts.AccountsModel { }
delegate: Kirigami.SwipeListItem {
id: accountDelegate
width: ListView.view.width
contentItem: Controls.Label {
text: {
if (model.displayName.length > 0 && model.providerName.length > 0) {
return i18n("%1 (%2)", model.displayName, model.providerName)
} else if (model.displayName.length > 0) {
return model.displayName
} else {
return i18n("%1 account", model.providerName)
}
contentItem: RowLayout {
implicitWidth: accountDelegate.ListView.view.width
implicitHeight: Kirigami.Units.iconSizes.large + Kirigami.Units.smallSpacing * 2
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
source: model.iconName
Layout.preferredWidth: Kirigami.Units.iconSizes.large
Layout.preferredHeight: Kirigami.Units.iconSizes.large
}
OA.Account {
id: account
objectHandle: model.accountHandle
Controls.Label {
Layout.fillWidth: true
text: {
if (model.displayName.length > 0 && model.providerName.length > 0) {
return i18n("%1 (%2)", model.displayName, model.providerName)
} else if (model.displayName.length > 0) {
return model.displayName
} else {
return i18n("%1 account", model.providerName)
}
}
}
}
actions: [
......@@ -67,56 +65,36 @@ ScrollViewKCM {
text: i18nc("Tooltip for an action which will offer the user to remove the mentioned account", "Remove %1", accountDelegate.contentItem.text)
iconName: "edit-delete-remove"
onTriggered: {
accountRemovalDlg.account = account;
accountRemovalDlg.displayName = model.displayName;
accountRemovalDlg.providerName = model.providerName;
accountRemovalDlg.open();
accountRemover.accountId = model.id;
accountRemover.displayName = model.displayName;
accountRemover.providerName = model.providerName;
accountRemover.open();
}
}
]
onClicked: kcm.push("AvailableServices.qml", {accountId: model.accountId})
onClicked: kcm.push("AccountDetails.qml", {model: model.services})
}
Controls.Label {
anchors {
fill: parent
margins: Kirigami.Units.largeSpacing
}
verticalAlignment: Text.AlignVCenter
visible: view.count === 0
anchors.fill: parent
clip: true
enabled: false
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
visible: parent.count === 0
opacity: 0.5
text: i18nc("A text shown when a user has not yet added any accounts", "You have not added any accounts yet.\nClick the \"Add new Account\" button below to do so.")
text: i18nc("A text shown when a user has not yet added any accounts", "You have not added any accounts yet.\n\nClick on \"Add New Account...\" below to do so.")
}
}
MessageBoxSheet {
id: accountRemovalDlg
RemoveAccountDialog {
id: accountRemover
parent: kaccountsRoot
property QtObject account
property string displayName
property string providerName
title: i18nc("The title for a dialog which lets you remove an account", "Remove Account?")
text: {
if (accountRemovalDlg.displayName.length > 0 && accountRemovalDlg.providerName.length > 0) {
return i18nc("The text for a dialog which lets you remove an account when both provider name and account name are available", "Are you sure you wish to remove the \"%1\" account \"%2\"?", accountRemovalDlg.providerName, accountRemovalDlg.displayName)
} else if (accountRemovalDlg.displayName.length > 0) {
return i18nc("The text for a dialog which lets you remove an account when only the account name is available", "Are you sure you wish to remove the account \"%1\"?", accountRemovalDlg.displayName)
} else {
return i18nc("The text for a dialog which lets you remove an account when only the provider name is available", "Are you sure you wish to remove this \"%1\" account?", accountRemovalDlg.providerName)
}
}
actions: [
Kirigami.Action {
text: i18nc("The label for a button which will cause the removal of a specified account", "Remove Account")
onTriggered: { accountRemovalDlg.account.remove(); }
}
]
}
footer: RowLayout {
Controls.Button {
Layout.alignment: Qt.AlignRight
text: i18n("Add new Account")
text: i18n("Add New Account...")
icon.name: "contact-new"
onClicked: kcm.push("AvailableAccounts.qml")
}
......
/*
* Copyright 2019 Nicolas Fella <nicolas.fella@gmx.de>
* Copyright 2020 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
......@@ -17,28 +18,67 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import QtQuick 2.7
import QtQuick.Layouts 1.11
import QtQuick.Controls 2.0 as Controls
import QtQuick 2.12
import QtQuick.Controls 2.12 as Controls
import QtQuick.Layouts 1.12
import org.kde.kirigami 2.4 as Kirigami
import org.kde.kaccounts 1.0
import org.kde.kcm 1.2
import Ubuntu.OnlineAccounts 0.1 as OA
import org.kde.kaccounts 1.2 as KAccounts
ScrollViewKCM {
id: root
title: i18n("Add new Account")
title: i18n("Add New Account")
view: ListView {
model: OA.ProviderModel {}
delegate: Kirigami.BasicListItem {
icon: model.iconName
label: model.displayName
width: parent.width
model: KAccounts.ProvidersModel {}
delegate: Kirigami.AbstractListItem {
id: accountDelegate
width: ListView.view.width
enabled: model.supportsMultipleAccounts === true || model.accountsCount === 0
contentItem: RowLayout {
implicitWidth: accountDelegate.ListView.view.width
implicitHeight: Kirigami.Units.iconSizes.large + Kirigami.Units.smallSpacing * 2
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
source: model.iconName
Layout.preferredWidth: Kirigami.Units.iconSizes.large
Layout.preferredHeight: Kirigami.Units.iconSizes.large
Item {
visible: model.accountsCount > 0
anchors {
bottom: parent.bottom
right: parent.right
}
height: parent.height / 3
width: height
Rectangle {
anchors.fill: parent
radius: height / 2
color: Kirigami.Theme.highlightColor
border {
width: 1
color: Kirigami.Theme.highlightedTextColor
}
}
Controls.Label {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Kirigami.Theme.highlightedTextColor
text: model.accountsCount
}
}
}
Controls.Label {
Layout.fillWidth: true
text: model.displayName + "\n" + model.description;
}
}
onClicked: {
var job = jobComponent.createObject(root, { "providerName": providerId })
var job = jobComponent.createObject(root, { "providerName": model.name })
job.start()
}
}
......@@ -46,7 +86,7 @@ ScrollViewKCM {
Component {
id: jobComponent
CreateAccountJob {
KAccounts.CreateAccountJob {
onFinished: kcm.pop()
}
}
......
......@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import QtQuick 2.7
import QtQuick.Controls 2.11 as QtControls
import QtQuick.Layouts 1.11 as QtLayouts
import QtQuick 2.12
import QtQuick.Controls 2.12 as QtControls
import QtQuick.Layouts 1.12 as QtLayouts
import org.kde.kirigami 2.7 as Kirigami
......
/*
* Copyright 2020 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import QtQuick 2.12
import QtQuick.Layouts 1.12
import org.kde.kirigami 2.7 as Kirigami
import org.kde.kaccounts 1.2 as KAccounts
MessageBoxSheet {
id: component
property int accountId
property string displayName
property string providerName
signal accountRemoved()
title: i18nc("The title for a dialog which lets you remove an account", "Remove Account?")
text: {
if (displayName.length > 0 && providerName.length > 0) {
return i18nc("The text for a dialog which lets you remove an account when both provider name and account name are available", "Are you sure you wish to remove the \"%1\" account \"%2\"?", providerName, displayName)
} else if (displayName.length > 0) {
return i18nc("The text for a dialog which lets you remove an account when only the account name is available", "Are you sure you wish to remove the account \"%1\"?", displayName)
} else {
return i18nc("The text for a dialog which lets you remove an account when only the provider name is available", "Are you sure you wish to remove this \"%1\" account?", providerName)
}
}
actions: [
Kirigami.Action {
text: i18nc("The label for a button which will cause the removal of a specified account", "Remove Account")
onTriggered: {
var job = accountRemovalJob.createObject(component, { "accountId": component.accountId });
job.start();
}
Component {
id: accountRemovalJob
KAccounts.RemoveAccountJob {
onFinished: component.accountRemoved()
}
}
}
]