summaryrefslogtreecommitdiff
path: root/settings
diff options
context:
space:
mode:
authorAndrew Branson <andrew.branson@jolla.com>2026-04-03 22:55:30 +0200
committerAndrew Branson <andrew.branson@jolla.com>2026-04-04 11:55:25 +0200
commita35c9fa159173388d88ef77e1d31f53488aad094 (patch)
treee4691b5bbf054ca13e35d98d9df653bf9cdc0054 /settings
parent5f999f7a4712c4a4d1c89054b544064cfd4b769e (diff)
Generalize for all fediverse accounts
Diffstat (limited to 'settings')
-rw-r--r--settings/accounts-translations-plugin/accounts-translations-plugin.pro6
-rw-r--r--settings/accounts-translations-plugin/plugin.cpp182
-rw-r--r--settings/accounts-translations-plugin/qmldir4
-rw-r--r--settings/accounts/accounts.pro38
-rw-r--r--settings/accounts/providers/fediverse.provider (renamed from settings/accounts/providers/mastodon.provider)14
-rw-r--r--settings/accounts/services/fediverse-microblog.service (renamed from settings/accounts/services/mastodon-microblog.service)12
-rw-r--r--settings/accounts/services/fediverse-notifications.service (renamed from settings/accounts/services/mastodon-notifications.service)12
-rw-r--r--settings/accounts/services/fediverse-sharing.service (renamed from settings/accounts/services/mastodon-sharing.service)10
-rw-r--r--settings/accounts/ui/FediverseSettingsDisplay.qml (renamed from settings/accounts/ui/MastodonSettingsDisplay.qml)45
-rw-r--r--settings/accounts/ui/fediverse-settings.qml (renamed from settings/accounts/ui/mastodon-settings.qml)14
-rw-r--r--settings/accounts/ui/fediverse-update.qml (renamed from settings/accounts/ui/mastodon-update.qml)27
-rw-r--r--settings/accounts/ui/fediverse.qml (renamed from settings/accounts/ui/mastodon.qml)293
12 files changed, 514 insertions, 143 deletions
diff --git a/settings/accounts-translations-plugin/accounts-translations-plugin.pro b/settings/accounts-translations-plugin/accounts-translations-plugin.pro
index aad978f..faa5ae5 100644
--- a/settings/accounts-translations-plugin/accounts-translations-plugin.pro
+++ b/settings/accounts-translations-plugin/accounts-translations-plugin.pro
@@ -3,13 +3,13 @@
# SPDX-License-Identifier: BSD-3-Clause
TEMPLATE = lib
-TARGET = mastodonaccountstranslationsplugin
+TARGET = fediverseaccountstranslationsplugin
TARGET = $$qtLibraryTarget($$TARGET)
-MODULENAME = com/jolla/settings/accounts/mastodon
+MODULENAME = com/jolla/settings/accounts/fediverse
TARGETPATH = $$[QT_INSTALL_QML]/$$MODULENAME
-QT += qml
+QT += qml network
CONFIG += plugin
SOURCES += plugin.cpp
diff --git a/settings/accounts-translations-plugin/plugin.cpp b/settings/accounts-translations-plugin/plugin.cpp
index 4a1c651..e377b6c 100644
--- a/settings/accounts-translations-plugin/plugin.cpp
+++ b/settings/accounts-translations-plugin/plugin.cpp
@@ -5,10 +5,20 @@
*/
#include <QCoreApplication>
+#include <QCryptographicHash>
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
#include <QLocale>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
#include <QQmlEngine>
#include <QQmlExtensionPlugin>
+#include <QSaveFile>
+#include <QSet>
+#include <QStandardPaths>
#include <QTranslator>
+#include <QUrl>
#include <QtQml>
class AppTranslator : public QTranslator
@@ -27,10 +37,167 @@ public:
}
};
-class MastodonAccountsTranslationsPlugin : public QQmlExtensionPlugin
+class FediverseInstanceIconCache : public QObject
{
Q_OBJECT
- Q_PLUGIN_METADATA(IID "com.jolla.settings.accounts.mastodon")
+public:
+ explicit FediverseInstanceIconCache(QObject *parent = nullptr)
+ : QObject(parent)
+ , m_networkAccessManager(new QNetworkAccessManager(this))
+ {
+ }
+
+ Q_INVOKABLE QString cachedIconPath(const QString &apiHost) const
+ {
+ const QString normalizedHost = normalizeApiHost(apiHost);
+ if (normalizedHost.isEmpty()) {
+ return QString();
+ }
+
+ const QString prefix = iconPrefix(normalizedHost);
+ const QDir dir(cacheDirectory());
+ const QStringList matches = dir.entryList(QStringList() << (prefix + QStringLiteral(".*")), QDir::Files);
+ if (matches.isEmpty()) {
+ return QString();
+ }
+
+ return dir.absoluteFilePath(matches.first());
+ }
+
+ Q_INVOKABLE void cacheIcon(const QString &apiHost, const QString &iconUrl)
+ {
+ const QString normalizedHost = normalizeApiHost(apiHost);
+ const QUrl normalizedUrl = QUrl::fromUserInput(iconUrl);
+ if (normalizedHost.isEmpty() || !normalizedUrl.isValid() || normalizedUrl.scheme().isEmpty()) {
+ emit iconError(apiHost);
+ return;
+ }
+
+ const QString existingPath = cachedIconPath(normalizedHost);
+ if (!existingPath.isEmpty()) {
+ emit iconReady(normalizedHost, existingPath);
+ return;
+ }
+
+ if (m_hostsInFlight.contains(normalizedHost)) {
+ return;
+ }
+
+ QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(normalizedUrl));
+ reply->setProperty("apiHost", normalizedHost);
+ connect(reply, &QNetworkReply::finished, this, [this, reply]() { handleReply(reply); });
+ m_hostsInFlight.insert(normalizedHost);
+ }
+
+Q_SIGNALS:
+ void iconReady(const QString &apiHost, const QString &iconPath);
+ void iconError(const QString &apiHost);
+
+private:
+ static QString normalizeApiHost(const QString &rawHost)
+ {
+ QString host = rawHost.trimmed();
+ if (host.isEmpty()) {
+ return QString();
+ }
+ if (!host.startsWith(QLatin1String("https://")) && !host.startsWith(QLatin1String("http://"))) {
+ host.prepend(QStringLiteral("https://"));
+ }
+
+ const QUrl url(host);
+ if (!url.isValid() || url.host().isEmpty()) {
+ return QString();
+ }
+
+ QString normalized = QString::fromLatin1(url.toEncoded(QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment));
+ if (normalized.endsWith(QLatin1Char('/'))) {
+ normalized.chop(1);
+ }
+ return normalized;
+ }
+
+ static QString cacheDirectory()
+ {
+ const QString base = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
+ return base + QStringLiteral("/sailfish-account-fediverse/icons");
+ }
+
+ static QString iconPrefix(const QString &apiHost)
+ {
+ return QString::fromLatin1(QCryptographicHash::hash(apiHost.toUtf8(), QCryptographicHash::Sha1).toHex());
+ }
+
+ static QString extensionForReply(QNetworkReply *reply)
+ {
+ const QString suffix = QFileInfo(reply->url().path()).suffix().trimmed().toLower();
+ if (!suffix.isEmpty()) {
+ return suffix;
+ }
+
+ const QString contentType = QString::fromLatin1(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray()).toLower();
+ if (contentType.contains(QLatin1String("png"))) {
+ return QStringLiteral("png");
+ }
+ if (contentType.contains(QLatin1String("jpeg")) || contentType.contains(QLatin1String("jpg"))) {
+ return QStringLiteral("jpg");
+ }
+ if (contentType.contains(QLatin1String("webp"))) {
+ return QStringLiteral("webp");
+ }
+ if (contentType.contains(QLatin1String("svg"))) {
+ return QStringLiteral("svg");
+ }
+ return QStringLiteral("img");
+ }
+
+ void handleReply(QNetworkReply *reply)
+ {
+ const QString apiHost = reply->property("apiHost").toString();
+ m_hostsInFlight.remove(apiHost);
+
+ const int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ const bool success = reply->error() == QNetworkReply::NoError && httpCode >= 200 && httpCode < 300;
+ const QString extension = extensionForReply(reply);
+ const QByteArray data = reply->readAll();
+ reply->deleteLater();
+
+ if (!success || data.isEmpty()) {
+ emit iconError(apiHost);
+ return;
+ }
+
+ const QString dirPath = cacheDirectory();
+ QDir dir;
+ if (!dir.mkpath(dirPath)) {
+ emit iconError(apiHost);
+ return;
+ }
+
+ const QString prefix = iconPrefix(apiHost);
+ const QDir cacheDir(dirPath);
+ const QStringList matches = cacheDir.entryList(QStringList() << (prefix + QStringLiteral(".*")), QDir::Files);
+ for (const QString &match : matches) {
+ QFile::remove(cacheDir.absoluteFilePath(match));
+ }
+
+ const QString filePath = cacheDir.absoluteFilePath(prefix + QStringLiteral(".") + extension);
+ QSaveFile file(filePath);
+ if (!file.open(QIODevice::WriteOnly) || file.write(data) != data.size() || !file.commit()) {
+ emit iconError(apiHost);
+ return;
+ }
+
+ emit iconReady(apiHost, filePath);
+ }
+
+ QNetworkAccessManager *m_networkAccessManager;
+ QSet<QString> m_hostsInFlight;
+};
+
+class FediverseAccountsTranslationsPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "com.jolla.settings.accounts.fediverse")
public:
void initializeEngine(QQmlEngine *engine, const char *uri) override
@@ -38,18 +205,19 @@ public:
Q_UNUSED(uri)
AppTranslator *engineeringEnglish = new AppTranslator(engine);
- engineeringEnglish->load("settings-accounts-mastodon_eng_en", "/usr/share/translations");
+ engineeringEnglish->load("settings-accounts-fediverse_eng_en", "/usr/share/translations");
AppTranslator *translator = new AppTranslator(engine);
- translator->load(QLocale(), "settings-accounts-mastodon", "-", "/usr/share/translations");
+ translator->load(QLocale(), "settings-accounts-fediverse", "-", "/usr/share/translations");
}
void registerTypes(const char *uri) override
{
- Q_ASSERT(QLatin1String(uri) == QLatin1String("com.jolla.settings.accounts.mastodon"));
- qmlRegisterUncreatableType<MastodonAccountsTranslationsPlugin>(uri, 1, 0,
- "MastodonTranslationPlugin",
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("com.jolla.settings.accounts.fediverse"));
+ qmlRegisterUncreatableType<FediverseAccountsTranslationsPlugin>(uri, 1, 0,
+ "FediverseTranslationPlugin",
QString());
+ qmlRegisterType<FediverseInstanceIconCache>(uri, 1, 0, "FediverseInstanceIconCache");
}
};
diff --git a/settings/accounts-translations-plugin/qmldir b/settings/accounts-translations-plugin/qmldir
index cf96622..69f186b 100644
--- a/settings/accounts-translations-plugin/qmldir
+++ b/settings/accounts-translations-plugin/qmldir
@@ -1,4 +1,4 @@
# Copyright (C) 2013-2026 Jolla Ltd.
-module com.jolla.settings.accounts.mastodon
-plugin mastodonaccountstranslationsplugin
+module com.jolla.settings.accounts.fediverse
+plugin fediverseaccountstranslationsplugin
diff --git a/settings/accounts/accounts.pro b/settings/accounts/accounts.pro
index 37982a3..1c80cf9 100644
--- a/settings/accounts/accounts.pro
+++ b/settings/accounts/accounts.pro
@@ -4,8 +4,8 @@
TEMPLATE = aux
-TS_FILE = $$OUT_PWD/settings-accounts-mastodon.ts
-EE_QM = $$OUT_PWD/settings-accounts-mastodon_eng_en.qm
+TS_FILE = $$OUT_PWD/settings-accounts-fediverse.ts
+EE_QM = $$OUT_PWD/settings-accounts-fediverse_eng_en.qm
ts.commands += lupdate $$PWD/ui -ts $$TS_FILE
ts.CONFIG += no_check_exist no_link
@@ -30,29 +30,29 @@ QMAKE_EXTRA_TARGETS += ts engineering_english
PRE_TARGETDEPS += ts engineering_english
OTHER_FILES += \
- $$PWD/providers/mastodon.provider \
- $$PWD/services/mastodon-microblog.service \
- $$PWD/services/mastodon-notifications.service \
- $$PWD/services/mastodon-sharing.service \
- $$PWD/ui/MastodonSettingsDisplay.qml \
- $$PWD/ui/mastodon.qml \
- $$PWD/ui/mastodon-settings.qml \
- $$PWD/ui/mastodon-update.qml
-
-provider.files += $$PWD/providers/mastodon.provider
+ $$PWD/providers/fediverse.provider \
+ $$PWD/services/fediverse-microblog.service \
+ $$PWD/services/fediverse-notifications.service \
+ $$PWD/services/fediverse-sharing.service \
+ $$PWD/ui/FediverseSettingsDisplay.qml \
+ $$PWD/ui/fediverse.qml \
+ $$PWD/ui/fediverse-settings.qml \
+ $$PWD/ui/fediverse-update.qml
+
+provider.files += $$PWD/providers/fediverse.provider
provider.path = /usr/share/accounts/providers/
services.files += \
- $$PWD/services/mastodon-microblog.service \
- $$PWD/services/mastodon-notifications.service \
- $$PWD/services/mastodon-sharing.service
+ $$PWD/services/fediverse-microblog.service \
+ $$PWD/services/fediverse-notifications.service \
+ $$PWD/services/fediverse-sharing.service
services.path = /usr/share/accounts/services/
ui.files += \
- $$PWD/ui/MastodonSettingsDisplay.qml \
- $$PWD/ui/mastodon.qml \
- $$PWD/ui/mastodon-settings.qml \
- $$PWD/ui/mastodon-update.qml
+ $$PWD/ui/FediverseSettingsDisplay.qml \
+ $$PWD/ui/fediverse.qml \
+ $$PWD/ui/fediverse-settings.qml \
+ $$PWD/ui/fediverse-update.qml
ui.path = /usr/share/accounts/ui/
INSTALLS += provider services ui ts_install engineering_english_install
diff --git a/settings/accounts/providers/mastodon.provider b/settings/accounts/providers/fediverse.provider
index 422c231..bb6e329 100644
--- a/settings/accounts/providers/mastodon.provider
+++ b/settings/accounts/providers/fediverse.provider
@@ -2,11 +2,11 @@
<!-- SPDX-FileCopyrightText: 2013 - 2026 Jolla Ltd. -->
<!-- SPDX-License-Identifier: BSD-3-Clause -->
<!DOCTYPE provider>
-<provider version="1.0" id="mastodon">
- <translations>/usr/share/translations/settings-accounts-mastodon</translations>
- <name>Mastodon</name>
- <description>Mastodon social network</description>
- <icon>image://theme/icon-l-mastodon</icon>
+<provider version="1.0" id="fediverse">
+ <translations>/usr/share/translations/settings-accounts-fediverse</translations>
+ <name>Fediverse</name>
+ <description>Fediverse social network</description>
+ <icon>image://theme/icon-l-fediverse</icon>
<template>
<group name="auth">
@@ -25,11 +25,11 @@
</group>
</group>
<group name="api">
- <setting name="Host">https://mastodon.social</setting>
+ <setting name="Host">mastodon.social</setting>
</group>
</template>
<tags>
- <tag>user-group:account-mastodon</tag>
+ <tag>user-group:account-fediverse</tag>
</tags>
</provider>
diff --git a/settings/accounts/services/mastodon-microblog.service b/settings/accounts/services/fediverse-microblog.service
index 527f70f..9070ec1 100644
--- a/settings/accounts/services/mastodon-microblog.service
+++ b/settings/accounts/services/fediverse-microblog.service
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- SPDX-FileCopyrightText: 2013 - 2026 Jolla Ltd. -->
<!-- SPDX-License-Identifier: BSD-3-Clause -->
-<service id="mastodon-microblog">
+<service id="fediverse-microblog">
<type>microblogging</type>
- <translations>/usr/share/translations/settings-accounts-mastodon</translations>
+ <translations>/usr/share/translations/settings-accounts-fediverse</translations>
<name>Posts</name>
- <icon>image://theme/icon-l-mastodon</icon>
- <provider>mastodon</provider>
+ <icon>image://theme/icon-l-fediverse</icon>
+ <provider>fediverse</provider>
<template>
- <setting name="sync_profile_templates" type="as">["mastodon.Posts"]</setting>
+ <setting name="sync_profile_templates" type="as">["fediverse.Posts"]</setting>
<group name="auth">
<setting name="method">oauth2</setting>
<setting name="mechanism">web_server</setting>
@@ -26,7 +26,7 @@
</group>
</group>
<group name="api">
- <setting name="Host">https://mastodon.social</setting>
+ <setting name="Host">mastodon.social</setting>
</group>
</template>
</service>
diff --git a/settings/accounts/services/mastodon-notifications.service b/settings/accounts/services/fediverse-notifications.service
index f5471c9..ea1b59b 100644
--- a/settings/accounts/services/mastodon-notifications.service
+++ b/settings/accounts/services/fediverse-notifications.service
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- SPDX-FileCopyrightText: 2013 - 2026 Jolla Ltd. -->
<!-- SPDX-License-Identifier: BSD-3-Clause -->
-<service id="mastodon-notifications">
+<service id="fediverse-notifications">
<type>microblogging</type>
- <translations>/usr/share/translations/settings-accounts-mastodon</translations>
+ <translations>/usr/share/translations/settings-accounts-fediverse</translations>
<name>Notifications</name>
- <icon>image://theme/icon-l-mastodon</icon>
- <provider>mastodon</provider>
+ <icon>image://theme/icon-l-fediverse</icon>
+ <provider>fediverse</provider>
<template>
- <setting name="sync_profile_templates" type="as">["mastodon.Notifications"]</setting>
+ <setting name="sync_profile_templates" type="as">["fediverse.Notifications"]</setting>
<group name="auth">
<setting name="method">oauth2</setting>
<setting name="mechanism">web_server</setting>
@@ -26,7 +26,7 @@
</group>
</group>
<group name="api">
- <setting name="Host">https://mastodon.social</setting>
+ <setting name="Host">mastodon.social</setting>
</group>
</template>
</service>
diff --git a/settings/accounts/services/mastodon-sharing.service b/settings/accounts/services/fediverse-sharing.service
index fdf342a..f66f11b 100644
--- a/settings/accounts/services/mastodon-sharing.service
+++ b/settings/accounts/services/fediverse-sharing.service
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- SPDX-FileCopyrightText: 2013 - 2026 Jolla Ltd. -->
<!-- SPDX-License-Identifier: BSD-3-Clause -->
-<service id="mastodon-sharing">
+<service id="fediverse-sharing">
<type>sharing</type>
- <translations>/usr/share/translations/settings-accounts-mastodon</translations>
+ <translations>/usr/share/translations/settings-accounts-fediverse</translations>
<name>Sharing</name>
- <icon>image://theme/icon-l-mastodon</icon>
- <provider>mastodon</provider>
+ <icon>image://theme/icon-l-fediverse</icon>
+ <provider>fediverse</provider>
<template>
<group name="auth">
@@ -25,7 +25,7 @@
</group>
</group>
<group name="api">
- <setting name="Host">https://mastodon.social</setting>
+ <setting name="Host">mastodon.social</setting>
</group>
</template>
</service>
diff --git a/settings/accounts/ui/MastodonSettingsDisplay.qml b/settings/accounts/ui/FediverseSettingsDisplay.qml
index 13ac06c..7494460 100644
--- a/settings/accounts/ui/MastodonSettingsDisplay.qml
+++ b/settings/accounts/ui/FediverseSettingsDisplay.qml
@@ -8,7 +8,7 @@ import QtQuick 2.0
import Sailfish.Silica 1.0
import Sailfish.Accounts 1.0
import com.jolla.settings.accounts 1.0
-import com.jolla.settings.accounts.mastodon 1.0
+import com.jolla.settings.accounts.fediverse 1.0
import org.nemomobile.configuration 1.0
StandardAccountSettingsDisplay {
@@ -16,6 +16,10 @@ StandardAccountSettingsDisplay {
settingsModified: true
property bool postsServiceEnabled: false
+ property string instanceTitle: {
+ var value = root.account.configurationValues("")["instance/Title"]
+ return value ? value.toString().trim() : ""
+ }
function refreshDescriptionEditor() {
var description = root.account.configurationValues("")["description"]
@@ -38,11 +42,14 @@ StandardAccountSettingsDisplay {
}
function _providerDisplayName() {
+ if (instanceTitle.length > 0) {
+ return instanceTitle
+ }
+
var providerDisplayName = root.accountProvider && root.accountProvider.displayName
? root.accountProvider.displayName.toString().trim()
: ""
- //% "Mastodon"
- return providerDisplayName.length > 0 ? providerDisplayName : qsTrId("settings-accounts-mastodon-la-provider_name")
+ return providerDisplayName.length > 0 ? providerDisplayName : qsTrId("settings-accounts-fediverse-la-provider_name")
}
onAboutToSaveAccount: {
@@ -70,7 +77,7 @@ StandardAccountSettingsDisplay {
root.account.setConfigurationValue("", "default_credentials_username", editedDescription)
}
- // Keep account list title fixed to provider name.
+ // Keep account list title fixed to the discovered instance title.
root.account.displayName = providerDisplayName
if (eventsSyncSwitch.checked !== root.account.configurationValues("")["FeedViewAutoSync"]) {
@@ -111,23 +118,23 @@ StandardAccountSettingsDisplay {
id: syncServicesRepeater
TextSwitch {
checked: model.enabled
- text: model.serviceName === "mastodon-microblog"
+ text: model.serviceName === "fediverse-microblog"
//% "Posts"
- ? qsTrId("settings-accounts-mastodon-la-service_posts")
- : (model.serviceName === "mastodon-notifications"
+ ? qsTrId("settings-accounts-fediverse-la-service_posts")
+ : (model.serviceName === "fediverse-notifications"
//% "Notifications"
- ? qsTrId("settings-accounts-mastodon-la-service_notifications")
+ ? qsTrId("settings-accounts-fediverse-la-service_notifications")
: model.displayName)
- description: model.serviceName === "mastodon-microblog"
- //% "Show Mastodon posts in the Events view."
- ? qsTrId("settings-accounts-mastodon-la-service_posts_description")
- : (model.serviceName === "mastodon-notifications"
- //% "Show Mastodon notifications."
- ? qsTrId("settings-accounts-mastodon-la-service_notifications_description")
+ description: model.serviceName === "fediverse-microblog"
+ //% "Show Fediverse posts in the Events view."
+ ? qsTrId("settings-accounts-fediverse-la-service_posts_description")
+ : (model.serviceName === "fediverse-notifications"
+ //% "Show Fediverse notifications."
+ ? qsTrId("settings-accounts-fediverse-la-service_notifications_description")
: "")
visible: text.length > 0
onCheckedChanged: {
- if (model.serviceName === "mastodon-microblog") {
+ if (model.serviceName === "fediverse-microblog") {
root.postsServiceEnabled = checked
}
if (checked) {
@@ -142,10 +149,10 @@ StandardAccountSettingsDisplay {
TextSwitch {
id: eventsSyncSwitch
- //% "Sync Mastodon feed automatically"
- text: qsTrId("settings-accounts-mastodon-la-auto_sync_feed")
- //% "Fetch new posts periodically when browsing Events Mastodon feed."
- description: qsTrId("settings-accounts-mastodon-la-auto_sync_feed_description")
+ //% "Sync Fediverse feed automatically"
+ text: qsTrId("settings-accounts-fediverse-la-auto_sync_feed")
+ //% "Fetch new posts periodically when browsing Events Fediverse feed."
+ description: qsTrId("settings-accounts-fediverse-la-auto_sync_feed_description")
enabled: root.postsServiceEnabled
onCheckedChanged: {
diff --git a/settings/accounts/ui/mastodon-settings.qml b/settings/accounts/ui/fediverse-settings.qml
index 0538e44..25e0d99 100644
--- a/settings/accounts/ui/mastodon-settings.qml
+++ b/settings/accounts/ui/fediverse-settings.qml
@@ -13,11 +13,12 @@ AccountSettingsAgent {
id: root
property string accountSubtitle: {
- var description = account.configurationValues("")["description"]
- var detail = description ? description.toString().trim() : ""
+ var instanceDescription = account.configurationValues("")["instance/Description"]
+ var detail = instanceDescription ? instanceDescription.toString().trim() : ""
if (detail.length > 0) {
return detail
}
+
var apiHost = account.configurationValues("")["api/Host"]
var host = apiHost ? apiHost.toString().trim() : ""
host = host.replace(/^https?:\/\//i, "")
@@ -28,6 +29,13 @@ AccountSettingsAgent {
if (host.length > 0) {
return host
}
+
+ var description = account.configurationValues("")["description"]
+ var handle = description ? description.toString().trim() : ""
+ if (handle.length > 0) {
+ return handle
+ }
+
var displayName = account.displayName ? account.displayName.toString().trim() : ""
if (displayName.length > 0) {
return displayName
@@ -89,7 +97,7 @@ AccountSettingsAgent {
description: root.accountSubtitle
}
- MastodonSettingsDisplay {
+ FediverseSettingsDisplay {
id: settingsDisplay
anchors.top: header.bottom
accountManager: root.accountManager
diff --git a/settings/accounts/ui/mastodon-update.qml b/settings/accounts/ui/fediverse-update.qml
index 2485a83..fe75089 100644
--- a/settings/accounts/ui/mastodon-update.qml
+++ b/settings/accounts/ui/fediverse-update.qml
@@ -8,7 +8,7 @@ import QtQuick 2.0
import Sailfish.Silica 1.0
import Sailfish.Accounts 1.0
import com.jolla.settings.accounts 1.0
-import com.jolla.settings.accounts.mastodon 1.0
+import com.jolla.settings.accounts.fediverse 1.0
AccountCredentialsAgent {
id: root
@@ -16,11 +16,12 @@ AccountCredentialsAgent {
property bool _started
readonly property string callbackUri: "http://ipv4.jolla.com/online/status.html"
+ readonly property string defaultServerHost: "mastodon.social"
function normalizeApiHost(rawHost) {
var host = rawHost ? rawHost.trim() : ""
if (host.length === 0) {
- host = "https://mastodon.social"
+ host = defaultServerHost
}
host = host.replace(/^https?:\/\//i, "")
@@ -31,7 +32,7 @@ AccountCredentialsAgent {
host = host.replace(/\/+$/, "")
if (host.length === 0) {
- host = "mastodon.social"
+ host = defaultServerHost
}
return "https://" + host.toLowerCase()
}
@@ -68,7 +69,7 @@ AccountCredentialsAgent {
return ""
}
- function _formatMastodonAccountId(accountName, apiHost) {
+ function _formatFediverseAccountId(accountName, apiHost) {
var value = accountName ? accountName.toString().trim() : ""
if (value.length === 0) {
return ""
@@ -99,7 +100,7 @@ AccountCredentialsAgent {
return token ? token.toString().trim() : ""
}
- function _isMastodonAccountId(value) {
+ function _isFediverseAccountId(value) {
var text = value ? value.toString().trim() : ""
return /^@[^@]+@[^@]+$/.test(text)
}
@@ -112,7 +113,7 @@ AccountCredentialsAgent {
function _saveDescription(description) {
if (description.length > 0) {
account.setConfigurationValue("", "description", description)
- if (_isMastodonAccountId(description)) {
+ if (_isFediverseAccountId(description)) {
account.setConfigurationValue("", "default_credentials_username", description)
}
}
@@ -121,9 +122,9 @@ AccountCredentialsAgent {
}
function _updateDescription(responseData) {
- var config = account.configurationValues("mastodon-microblog")
+ var config = account.configurationValues("fediverse-microblog")
var apiHost = normalizeApiHost(_valueFromServiceConfig(config, "api/Host"))
- var description = _formatMastodonAccountId(_extractAccountName(responseData), apiHost)
+ var description = _formatFediverseAccountId(_extractAccountName(responseData), apiHost)
if (description.length > 0) {
_saveDescription(description)
return
@@ -145,7 +146,7 @@ AccountCredentialsAgent {
if (xhr.status >= 200 && xhr.status < 300) {
try {
var response = JSON.parse(xhr.responseText)
- fetchedDescription = _formatMastodonAccountId(_extractAccountName(response), apiHost)
+ fetchedDescription = _formatFediverseAccountId(_extractAccountName(response), apiHost)
} catch (err) {
}
}
@@ -167,7 +168,7 @@ AccountCredentialsAgent {
return
}
- var config = account.configurationValues("mastodon-microblog")
+ var config = account.configurationValues("fediverse-microblog")
var apiHost = normalizeApiHost(_valueFromServiceConfig(config, "api/Host"))
var oauthHost = _valueFromServiceConfig(config, "auth/oauth2/web_server/Host")
if (oauthHost.length === 0) {
@@ -177,8 +178,8 @@ AccountCredentialsAgent {
var clientId = _valueFromServiceConfig(config, "auth/oauth2/web_server/ClientId")
var clientSecret = _valueFromServiceConfig(config, "auth/oauth2/web_server/ClientSecret")
if (clientId.length === 0 || clientSecret.length === 0) {
- //% "Missing Mastodon OAuth client credentials"
- credentialsUpdateError(qsTrId("settings-accounts-mastodon-la-missing_client_credentials"))
+ //% "Missing Fediverse OAuth client credentials"
+ credentialsUpdateError(qsTrId("settings-accounts-fediverse-la-missing_client_credentials"))
return
}
@@ -194,7 +195,7 @@ AccountCredentialsAgent {
"Scope": ["read", "write"],
"RedirectUri": callbackUri
}
- initialPage.prepareAccountCredentialsUpdate(account, root.accountProvider, "mastodon-microblog", sessionData)
+ initialPage.prepareAccountCredentialsUpdate(account, root.accountProvider, "fediverse-microblog", sessionData)
}
Account {
diff --git a/settings/accounts/ui/mastodon.qml b/settings/accounts/ui/fediverse.qml
index 129fda5..3f5968a 100644
--- a/settings/accounts/ui/mastodon.qml
+++ b/settings/accounts/ui/fediverse.qml
@@ -8,7 +8,7 @@ import QtQuick 2.0
import Sailfish.Silica 1.0
import Sailfish.Accounts 1.0
import com.jolla.settings.accounts 1.0
-import com.jolla.settings.accounts.mastodon 1.0
+import com.jolla.settings.accounts.fediverse 1.0
AccountCreationAgent {
id: root
@@ -18,10 +18,13 @@ AccountCreationAgent {
property QtObject _accountSetup
property string _pendingApiHost
+ property var _pendingInstanceContext: ({})
property bool _registering
readonly property string callbackUri: "http://ipv4.jolla.com/online/status.html"
- readonly property string defaultApiHost: "https://mastodon.social"
+ readonly property string defaultServerHost: "mastodon.social"
+ readonly property string defaultApiHost: normalizeApiHost(defaultServerHost)
+ readonly property string genericIconPath: "image://theme/icon-l-fediverse"
function normalizeApiHost(rawHost) {
var host = rawHost ? rawHost.trim() : ""
@@ -46,6 +49,27 @@ AccountCreationAgent {
return apiHost.replace(/^https?:\/\//i, "")
}
+ function _trimmedString(value) {
+ return value ? value.toString().trim() : ""
+ }
+
+ function _normalizeResourceUrl(rawUrl, apiHost) {
+ var value = _trimmedString(rawUrl)
+ if (value.length === 0) {
+ return ""
+ }
+ if (/^https?:\/\//i.test(value)) {
+ return value
+ }
+ if (value.indexOf("//") === 0) {
+ return "https:" + value
+ }
+ if (value.charAt(0) === "/") {
+ return apiHost + value
+ }
+ return apiHost + "/" + value.replace(/^\/+/, "")
+ }
+
function _displayName(apiHost) {
return oauthHost(apiHost)
}
@@ -58,6 +82,78 @@ AccountCreationAgent {
return _displayName(defaultApiHost)
}
+ function _instanceContext(apiHost) {
+ var normalizedApiHost = normalizeApiHost(apiHost)
+ var title = _fallbackDisplayName(normalizedApiHost)
+ return {
+ "apiHost": normalizedApiHost,
+ "oauthHost": oauthHost(normalizedApiHost),
+ "instanceTitle": title.length > 0 ? title : qsTrId("settings-accounts-fediverse-la-provider_name"),
+ "instanceDescription": "",
+ "instanceIconUrl": "",
+ "instanceIconPath": instanceIconCache.cachedIconPath(normalizedApiHost)
+ }
+ }
+
+ function _extractInstanceTitle(responseData, apiHost) {
+ var candidates = [
+ responseData ? responseData.title : "",
+ responseData ? responseData.name : "",
+ responseData ? responseData.uri : ""
+ ]
+ for (var i = 0; i < candidates.length; ++i) {
+ var value = _trimmedString(candidates[i])
+ if (value.length > 0) {
+ return value
+ }
+ }
+ return _fallbackDisplayName(apiHost)
+ }
+
+ function _extractInstanceDescription(responseData) {
+ var candidates = [
+ responseData ? responseData.short_description : "",
+ responseData ? responseData.description : "",
+ responseData ? responseData.shortDescription : ""
+ ]
+ for (var i = 0; i < candidates.length; ++i) {
+ var value = _trimmedString(candidates[i])
+ if (value.length > 0) {
+ return value
+ }
+ }
+ return ""
+ }
+
+ function _extractInstanceIconUrl(responseData, apiHost) {
+ if (!responseData) {
+ return ""
+ }
+
+ if (responseData.thumbnail) {
+ if (typeof responseData.thumbnail === "string") {
+ return _normalizeResourceUrl(responseData.thumbnail, apiHost)
+ }
+ if (responseData.thumbnail.url) {
+ return _normalizeResourceUrl(responseData.thumbnail.url, apiHost)
+ }
+ if (responseData.thumbnail.static_url) {
+ return _normalizeResourceUrl(responseData.thumbnail.static_url, apiHost)
+ }
+ }
+
+ if (responseData.icon) {
+ if (typeof responseData.icon === "string") {
+ return _normalizeResourceUrl(responseData.icon, apiHost)
+ }
+ if (responseData.icon.url) {
+ return _normalizeResourceUrl(responseData.icon.url, apiHost)
+ }
+ }
+
+ return ""
+ }
+
function _showRegistrationError(message, busyPage) {
_registering = false
accountCreationError(message)
@@ -79,12 +175,16 @@ AccountCreationAgent {
pageStack.replace(_oauthPage)
}
- function _registerClientApplication(apiHost, busyPage) {
+ function _registerClientApplication(context, busyPage) {
if (_registering) {
return
}
_registering = true
+ if (context.instanceIconUrl.length > 0 && context.instanceIconPath.length === 0) {
+ instanceIconCache.cacheIcon(context.apiHost, context.instanceIconUrl)
+ }
+
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
if (xhr.readyState !== XMLHttpRequest.DONE) {
@@ -92,8 +192,7 @@ AccountCreationAgent {
}
if (xhr.status < 200 || xhr.status >= 300) {
- //% "Failed to register Mastodon app for %1"
- _showRegistrationError(qsTrId("settings-accounts-mastodon-la-register_app_failed").arg(apiHost), busyPage)
+ _showRegistrationError(qsTrId("settings-accounts-fediverse-la-register_app_failed").arg(context.apiHost), busyPage)
return
}
@@ -101,37 +200,80 @@ AccountCreationAgent {
try {
response = JSON.parse(xhr.responseText)
} catch (err) {
- //% "Invalid Mastodon app registration response"
- _showRegistrationError(qsTrId("settings-accounts-mastodon-la-invalid_app_registration_response"), busyPage)
+ _showRegistrationError(qsTrId("settings-accounts-fediverse-la-invalid_app_registration_response"), busyPage)
return
}
if (!response.client_id || !response.client_secret) {
- //% "Mastodon app registration did not return credentials"
- _showRegistrationError(qsTrId("settings-accounts-mastodon-la-app_registration_missing_credentials"), busyPage)
+ _showRegistrationError(qsTrId("settings-accounts-fediverse-la-app_registration_missing_credentials"), busyPage)
return
}
- _showOAuthPage({
- "apiHost": apiHost,
- "oauthHost": oauthHost(apiHost),
- "clientId": response.client_id,
- "clientSecret": response.client_secret
- })
+ context.clientId = response.client_id
+ context.clientSecret = response.client_secret
+ _showOAuthPage(context)
}
var postData = []
- //% "Mastodon in SailfishOS"
- postData.push("client_name=" + encodeURIComponent(qsTrId("settings-accounts-mastodon-la-client_name")))
+ postData.push("client_name=" + encodeURIComponent(qsTrId("settings-accounts-fediverse-la-client_name")))
postData.push("redirect_uris=" + encodeURIComponent(callbackUri))
postData.push("scopes=" + encodeURIComponent("read write"))
postData.push("website=" + encodeURIComponent("https://sailfishos.org"))
- xhr.open("POST", apiHost + "/api/v1/apps")
+ xhr.open("POST", context.apiHost + "/api/v1/apps")
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
xhr.send(postData.join("&"))
}
+ function _discoverInstanceContext(apiHost, busyPage) {
+ var context = _instanceContext(apiHost)
+ _pendingInstanceContext = context
+
+ function continueWithContext() {
+ _pendingInstanceContext = context
+ _registerClientApplication(context, busyPage)
+ }
+
+ function loadEndpoint(paths, index) {
+ if (index >= paths.length) {
+ continueWithContext()
+ return
+ }
+
+ var xhr = new XMLHttpRequest()
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState !== XMLHttpRequest.DONE) {
+ return
+ }
+
+ if (xhr.status >= 200 && xhr.status < 300) {
+ try {
+ var response = JSON.parse(xhr.responseText)
+ context.instanceTitle = _extractInstanceTitle(response, context.apiHost)
+ context.instanceDescription = _extractInstanceDescription(response)
+ context.instanceIconUrl = _extractInstanceIconUrl(response, context.apiHost)
+ if (context.instanceIconUrl.length > 0) {
+ var cachedPath = instanceIconCache.cachedIconPath(context.apiHost)
+ if (cachedPath.length > 0) {
+ context.instanceIconPath = cachedPath
+ }
+ }
+ } catch (err) {
+ }
+ continueWithContext()
+ return
+ }
+
+ loadEndpoint(paths, index + 1)
+ }
+
+ xhr.open("GET", context.apiHost + paths[index])
+ xhr.send()
+ }
+
+ loadEndpoint(["/api/v2/instance", "/api/v1/instance"], 0)
+ }
+
function _extractAccountName(responseData) {
if (!responseData) {
return ""
@@ -160,7 +302,7 @@ AccountCreationAgent {
return ""
}
- function _formatMastodonAccountId(accountName, apiHost) {
+ function _formatFediverseAccountId(accountName, apiHost) {
var value = accountName ? accountName.toString().trim() : ""
if (value.length === 0) {
return ""
@@ -179,7 +321,7 @@ AccountCreationAgent {
return "@" + value + "@" + host
}
- function _isMastodonAccountId(value) {
+ function _isFediverseAccountId(value) {
var text = value ? value.toString().trim() : ""
return /^@[^@]+@[^@]+$/.test(text)
}
@@ -204,7 +346,11 @@ AccountCreationAgent {
"clientId": context.clientId,
"clientSecret": context.clientSecret,
"accessToken": _extractAccessToken(responseData),
- "accountDescription": _formatMastodonAccountId(_extractAccountName(responseData), context.apiHost)
+ "accountDescription": _formatFediverseAccountId(_extractAccountName(responseData), context.apiHost),
+ "instanceTitle": context.instanceTitle,
+ "instanceDescription": context.instanceDescription,
+ "instanceIconUrl": context.instanceIconUrl,
+ "instanceIconPath": context.instanceIconPath
}
_accountSetup = accountSetupComponent.createObject(root, props)
_accountSetup.done.connect(function() {
@@ -212,8 +358,7 @@ AccountCreationAgent {
_goToSettings(accountId)
})
_accountSetup.error.connect(function() {
- //% "Failed to finish Mastodon account setup"
- accountCreationError(qsTrId("settings-accounts-mastodon-la-account_setup_failed"))
+ accountCreationError(qsTrId("settings-accounts-fediverse-la-account_setup_failed"))
})
}
@@ -225,6 +370,20 @@ AccountCreationAgent {
pageStack.replace(_settingsDialog)
}
+ FediverseInstanceIconCache {
+ id: instanceIconCache
+
+ onIconReady: {
+ var normalizedHost = root.normalizeApiHost(apiHost)
+ if (root._pendingInstanceContext.apiHost === normalizedHost) {
+ root._pendingInstanceContext.instanceIconPath = iconPath
+ }
+ if (root._accountSetup && root._accountSetup.apiHost === normalizedHost) {
+ root._accountSetup.updateInstanceIcon(iconPath)
+ }
+ }
+ }
+
initialPage: Dialog {
id: setupDialog
@@ -240,7 +399,6 @@ AccountCreationAgent {
DialogHeader {
id: header
- //% "Sign in"
acceptText: qsTrId("settings-accounts-common-bt-sign_in")
}
@@ -259,19 +417,17 @@ AccountCreationAgent {
id: promptIcon
width: Theme.iconSizeMedium
height: Theme.iconSizeMedium
- source: "image://theme/icon-l-mastodon"
+ source: root.genericIconPath
fillMode: Image.PreserveAspectFit
sourceSize.width: width
sourceSize.height: height
}
- //: Prompt shown in account setup before OAuth sign-in.
Label {
width: parent.width - promptIcon.width - parent.spacing
wrapMode: Text.Wrap
color: Theme.highlightColor
- //% "Enter your Mastodon server, then sign in."
- text: qsTrId("settings-accounts-mastodon-la-enter_server_then_sign_in")
+ text: qsTrId("settings-accounts-fediverse-la-enter_server_then_sign_in")
}
}
@@ -279,9 +435,8 @@ AccountCreationAgent {
id: instanceField
x: Theme.horizontalPageMargin
width: parent.width - x * 2
- //% "Server"
- label: qsTrId("settings-accounts-mastodon-la-server")
- placeholderText: "mastodon.social"
+ label: qsTrId("settings-accounts-fediverse-la-server")
+ placeholderText: root.defaultServerHost
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhUrlCharactersOnly
EnterKey.iconSource: "image://theme/icon-m-enter-next"
EnterKey.onClicked: {
@@ -296,11 +451,10 @@ AccountCreationAgent {
Component {
id: busyComponent
AccountBusyPage {
- //% "Preparing Mastodon sign-in..."
- busyDescription: qsTrId("settings-accounts-mastodon-la-preparing_sign_in")
+ busyDescription: qsTrId("settings-accounts-fediverse-la-preparing_sign_in")
onStatusChanged: {
if (status === PageStatus.Active && root._pendingApiHost.length > 0) {
- root._registerClientApplication(root._pendingApiHost, this)
+ root._discoverInstanceContext(root._pendingApiHost, this)
}
}
}
@@ -322,7 +476,7 @@ AccountCreationAgent {
"Scope": ["read", "write"],
"RedirectUri": root.callbackUri
}
- prepareAccountCreation(root.accountProvider, "mastodon-microblog", sessionData)
+ prepareAccountCreation(root.accountProvider, "fediverse-microblog", sessionData)
}
onAccountCreated: {
@@ -347,6 +501,10 @@ AccountCreationAgent {
property string clientSecret
property string accessToken
property string accountDescription
+ property string instanceTitle
+ property string instanceDescription
+ property string instanceIconUrl
+ property string instanceIconPath
property bool hasConfigured
signal done()
@@ -368,31 +526,57 @@ AccountCreationAgent {
}
}
+ function _serviceNames() {
+ return ["fediverse-microblog", "fediverse-notifications", "fediverse-sharing"]
+ }
+
+ function _effectiveInstanceTitle() {
+ var title = root._trimmedString(instanceTitle)
+ return title.length > 0 ? title : qsTrId("settings-accounts-fediverse-la-provider_name")
+ }
+
+ function _effectiveIconPath() {
+ return root._trimmedString(instanceIconPath).length > 0 ? instanceIconPath : root.genericIconPath
+ }
+
+ function _applyIconPath(iconPath) {
+ instanceIconPath = iconPath
+ newAccount.setConfigurationValue("", "iconPath", _effectiveIconPath())
+ var services = _serviceNames()
+ for (var i = 0; i < services.length; ++i) {
+ newAccount.setConfigurationValue(services[i], "iconPath", _effectiveIconPath())
+ }
+ if (hasConfigured) {
+ newAccount.sync()
+ }
+ }
+
+ function updateInstanceIcon(iconPath) {
+ if (root._trimmedString(iconPath).length === 0) {
+ return
+ }
+ _applyIconPath(iconPath)
+ }
+
function configure() {
hasConfigured = true
- var services = ["mastodon-microblog", "mastodon-notifications", "mastodon-sharing"]
- var providerDisplayName = root.accountProvider && root.accountProvider.displayName
- ? root.accountProvider.displayName.toString().trim()
- : ""
- if (providerDisplayName.length === 0) {
- //% "Mastodon"
- providerDisplayName = qsTrId("settings-accounts-mastodon-la-provider_name")
- }
+ var services = _serviceNames()
+ var providerDisplayName = _effectiveInstanceTitle()
newAccount.displayName = providerDisplayName
newAccount.setConfigurationValue("", "api/Host", apiHost)
newAccount.setConfigurationValue("", "FeedViewAutoSync", true)
+ newAccount.setConfigurationValue("", "instance/Title", providerDisplayName)
+ newAccount.setConfigurationValue("", "instance/Description", root._trimmedString(instanceDescription))
+ newAccount.setConfigurationValue("", "instance/IconUrl", root._trimmedString(instanceIconUrl))
+ _applyIconPath(_effectiveIconPath())
+
if (accountDescription.length > 0) {
newAccount.setConfigurationValue("", "description", accountDescription)
- if (root._isMastodonAccountId(accountDescription)) {
+ if (root._isFediverseAccountId(accountDescription)) {
newAccount.setConfigurationValue("", "default_credentials_username", accountDescription)
}
- } else {
- var hostDisplayName = root._fallbackDisplayName(apiHost)
- if (hostDisplayName.length > 0) {
- newAccount.setConfigurationValue("", "description", hostDisplayName)
- }
}
for (var i = 0; i < services.length; ++i) {
@@ -412,6 +596,10 @@ AccountCreationAgent {
newAccount.enableWithService(services[j])
}
+ if (instanceIconUrl.length > 0 && instanceIconPath.length === 0) {
+ instanceIconCache.cacheIcon(apiHost, instanceIconUrl)
+ }
+
if (accountDescription.length > 0 || accessToken.length === 0) {
newAccount.sync()
return
@@ -426,11 +614,11 @@ AccountCreationAgent {
if (xhr.status >= 200 && xhr.status < 300) {
try {
var response = JSON.parse(xhr.responseText)
- var fetchedDescription = root._formatMastodonAccountId(root._extractAccountName(response), apiHost)
+ var fetchedDescription = root._formatFediverseAccountId(root._extractAccountName(response), apiHost)
if (fetchedDescription.length > 0) {
accountDescription = fetchedDescription
newAccount.setConfigurationValue("", "description", fetchedDescription)
- if (root._isMastodonAccountId(fetchedDescription)) {
+ if (root._isFediverseAccountId(fetchedDescription)) {
newAccount.setConfigurationValue("", "default_credentials_username", fetchedDescription)
}
}
@@ -472,7 +660,7 @@ AccountCreationAgent {
id: header
}
- MastodonSettingsDisplay {
+ FediverseSettingsDisplay {
id: settingsDisplay
anchors.top: header.bottom
accountManager: root.accountManager
@@ -488,5 +676,4 @@ AccountCreationAgent {
}
}
}
-
}