Warning, /multimedia/kasts/src/qml/Settings/SynchronizationSettingsPage.qml is written in an unsupported language. File is not indexed.

0001 /**
0002  * SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
0003  * SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 import QtQuick
0009 import QtQuick.Controls as Controls
0010 import QtQuick.Layouts
0011 
0012 import org.kde.kirigami as Kirigami
0013 import org.kde.kirigamiaddons.delegates as Delegates
0014 import org.kde.kirigamiaddons.formcard as FormCard
0015 
0016 import org.kde.kasts
0017 import org.kde.kasts.settings
0018 
0019 import ".."
0020 
0021 Kirigami.ScrollablePage {
0022     id: root
0023 
0024     leftPadding: 0
0025     rightPadding: 0
0026 
0027     ColumnLayout {
0028         spacing: 0
0029 
0030         FormCard.FormCard {
0031             Layout.fillWidth: true
0032 
0033             FormCard.FormTextDelegate {
0034                 id: accountStatus
0035                 text: i18n("Account")
0036                 description: Sync.syncEnabled ? i18n("Logged into account \"%1\" on server \"%2\"", Sync.username, (Sync.provider == SyncUtils.GPodderNet && Sync.hostname == "") ? "gpodder.net" : Sync.hostname) : i18n("Syncing disabled")
0037 
0038                 trailing: Controls.Button {
0039                     text: Sync.syncEnabled ? i18n("Logout") : i18n("Login")
0040                     onClicked: {
0041                         Sync.syncEnabled ? Sync.logout() : syncProviderOverlay.open();
0042                     }
0043                 }
0044             }
0045 
0046             FormCard.FormDelegateSeparator {}
0047 
0048             FormCard.FormTextDelegate {
0049                 id: manualSync
0050                 text: i18n("Manually sync")
0051 
0052                 trailing: Controls.Button {
0053                     text: i18n("Sync Now")
0054                     enabled: Sync.syncEnabled
0055                     onClicked: {
0056                         syncFeedsAndEpisodes.run();
0057                     }
0058                 }
0059             }
0060 
0061             FormCard.FormDelegateSeparator {}
0062 
0063             FormCard.FormTextDelegate {
0064                 id: lastFullSync
0065                 text: i18n("Last full sync with server")
0066                 description: Sync.lastSuccessfulDownloadSync
0067             }
0068 
0069             FormCard.FormDelegateSeparator {}
0070 
0071             FormCard.FormTextDelegate {
0072                 id: lastQuickUpload
0073                 text: i18n("Last quick upload to sync server")
0074                 description: Sync.lastSuccessfulUploadSync
0075             }
0076         }
0077 
0078         FormCard.FormHeader {
0079             title: i18n("Automatic syncing")
0080             Layout.fillWidth: true
0081         }
0082 
0083         FormCard.FormCard {
0084             Layout.fillWidth: true
0085 
0086             FormCard.FormCheckDelegate {
0087                 enabled: Sync.syncEnabled
0088                 checked: SettingsManager.refreshOnStartup
0089                 text: i18n("Do full sync on startup")
0090                 onToggled: {
0091                     SettingsManager.refreshOnStartup = checked;
0092                     SettingsManager.save();
0093                 }
0094             }
0095 
0096             FormCard.FormCheckDelegate {
0097                 enabled: Sync.syncEnabled
0098                 checked: SettingsManager.syncWhenUpdatingFeeds
0099                 text: i18n("Do full sync when fetching podcasts")
0100                 onToggled: {
0101                     SettingsManager.syncWhenUpdatingFeeds = checked;
0102                     SettingsManager.save();
0103                 }
0104             }
0105 
0106             FormCard.FormCheckDelegate {
0107                 enabled: Sync.syncEnabled
0108                 checked: SettingsManager.syncWhenPlayerstateChanges
0109                 text: i18n("Upload episode play positions on play/pause toggle")
0110                 onToggled: {
0111                     SettingsManager.syncWhenPlayerstateChanges = checked;
0112                     SettingsManager.save();
0113                 }
0114             }
0115         }
0116 
0117         FormCard.FormHeader {
0118             title: i18n("Advanced options")
0119             Layout.fillWidth: true
0120         }
0121 
0122         FormCard.FormCard {
0123             Layout.fillWidth: true
0124 
0125             FormCard.FormTextDelegate {
0126                 id: fetchAllEpisodeStates
0127                 text: i18n("Fetch all episode states from server")
0128 
0129                 trailing: Controls.Button {
0130                     text: i18n("Fetch")
0131                     enabled: Sync.syncEnabled
0132                     onClicked: {
0133                         forceSyncFeedsAndEpisodes.run();
0134                     }
0135                 }
0136             }
0137 
0138             FormCard.FormDelegateSeparator {}
0139 
0140             FormCard.FormTextDelegate {
0141                 id: fetchLocalEpisodeStates
0142                 text: i18n("Push all local episode states to server")
0143 
0144                 trailing: Controls.Button {
0145                     enabled: Sync.syncEnabled
0146                     text: i18n("Push")
0147                     onClicked: {
0148                         syncPushAllStatesDialog.open();
0149                     }
0150                 }
0151             }
0152         }
0153     }
0154 
0155     // This item can be used to trigger an update of all feeds; it will open an
0156     // overlay with options in case the operation is not allowed by the settings
0157     ConnectionCheckAction {
0158         id: syncFeedsAndEpisodes
0159 
0160         function action() {
0161             Sync.doRegularSync();
0162         }
0163     }
0164 
0165     // This item can be used to trigger an update of all feeds; it will open an
0166     // overlay with options in case the operation is not allowed by the settings
0167     ConnectionCheckAction {
0168         id: forceSyncFeedsAndEpisodes
0169 
0170         function action() {
0171             Sync.doForceSync();
0172         }
0173     }
0174 
0175     Kirigami.Dialog {
0176         id: syncPushAllStatesDialog
0177         preferredWidth: Kirigami.Units.gridUnit * 25
0178         padding: Kirigami.Units.largeSpacing
0179 
0180         showCloseButton: true
0181         standardButtons: Controls.DialogButtonBox.Ok | Controls.DialogButtonBox.Cancel
0182         closePolicy: Kirigami.Dialog.CloseOnEscape | Kirigami.Dialog.CloseOnPressOutside
0183 
0184         title: i18n("Push all local episode states to server?")
0185 
0186         onAccepted: {
0187             syncPushAllStatesDialog.close();
0188             syncPushAllStates.run();
0189         }
0190         onRejected: syncPushAllStatesDialog.close();
0191 
0192         RowLayout {
0193             spacing: Kirigami.Units.largeSpacing
0194             Kirigami.Icon {
0195                 Layout.preferredHeight: Kirigami.Units.gridUnit * 4
0196                 Layout.preferredWidth: Kirigami.Units.gridUnit * 4
0197                 source: Sync.provider === Sync.GPodderNextcloud ? "kaccounts-nextcloud" : "gpodder"
0198             }
0199             TextEdit {
0200                 Layout.fillWidth: true
0201                 Layout.fillHeight: true
0202                 readOnly: true
0203                 wrapMode: Text.WordWrap
0204                 text: i18n("Please note that pushing the playback state of all local episodes to the server might take a very long time and/or might overload the server. Also note that this action will overwrite all existing episode states on the server.\n\nContinue?")
0205                 color: Kirigami.Theme.textColor
0206                 Keys.onReturnPressed: accepted();
0207             }
0208         }
0209     }
0210 
0211     // This item can be used to trigger a push of all episode states to the server;
0212     // it will open an overlay with options in case the operation is not allowed by the settings
0213     ConnectionCheckAction {
0214         id: syncPushAllStates
0215 
0216         function action() {
0217             Sync.doSyncPushAll();
0218         }
0219     }
0220 
0221     Kirigami.Dialog {
0222         id: syncProviderOverlay
0223         preferredWidth: Kirigami.Units.gridUnit * 20
0224         standardButtons: Kirigami.Dialog.NoButton
0225 
0226         showCloseButton: true
0227 
0228         title: i18n("Select Sync Provider")
0229 
0230         ColumnLayout {
0231             spacing: 0
0232 
0233             Repeater {
0234                 focus: syncProviderOverlay.visible
0235 
0236                 model: ListModel {
0237                     id: providerModel
0238                 }
0239                 Component.onCompleted: {
0240                     providerModel.append({"name": i18n("gpodder.net"),
0241                                         "subtitle": i18n("Synchronize with official gpodder.net server"),
0242                                         "icon": "gpodder",
0243                                         "provider": Sync.GPodderNet});
0244                     providerModel.append({"name": i18n("GPodder Nextcloud"),
0245                                         "subtitle": i18n("Synchronize with GPodder Nextcloud app"),
0246                                         "icon": "kaccounts-nextcloud",
0247                                         "provider": Sync.GPodderNextcloud});
0248                 }
0249                 delegate: Delegates.RoundedItemDelegate {
0250                     id: syncProviderRepeaterDelegate
0251                     Layout.fillWidth: true
0252                     text: model.name
0253                     icon.name: model.icon
0254                     contentItem: Delegates.SubtitleContentItem {
0255                         itemDelegate: syncProviderRepeaterDelegate
0256                         subtitle: model.subtitle
0257                     }
0258                     Keys.onReturnPressed: clicked()
0259                     onClicked: {
0260                         Sync.provider = model.provider;
0261                         syncProviderOverlay.close();
0262                         syncLoginOverlay.open();
0263                     }
0264                 }
0265             }
0266         }
0267     }
0268 
0269     Kirigami.Dialog {
0270         id: syncLoginOverlay
0271         preferredWidth: Kirigami.Units.gridUnit * 25
0272         padding: Kirigami.Units.largeSpacing
0273 
0274         showCloseButton: true
0275         standardButtons: Controls.DialogButtonBox.Ok | Controls.DialogButtonBox.Cancel
0276         closePolicy: Kirigami.Dialog.CloseOnEscape | Kirigami.Dialog.CloseOnPressOutside
0277 
0278         title: i18n("Sync Login Credentials")
0279 
0280         onAccepted: {
0281             if (Sync.provider === Sync.GPodderNextcloud || customServerCheckBox.checked) {
0282                 Sync.hostname = hostnameField.text;
0283             } else {
0284                 Sync.hostname = ""
0285             }
0286             Sync.login(usernameField.text, passwordField.text);
0287             syncLoginOverlay.close();
0288         }
0289         onRejected: syncLoginOverlay.close();
0290 
0291         Column {
0292             spacing: Kirigami.Units.largeSpacing
0293             RowLayout {
0294                 width: parent.width
0295                 spacing: Kirigami.Units.largeSpacing
0296                 Kirigami.Icon {
0297                     Layout.preferredHeight: Kirigami.Units.gridUnit * 4
0298                     Layout.preferredWidth: Kirigami.Units.gridUnit * 4
0299                     source: Sync.provider === Sync.GPodderNextcloud ? "kaccounts-nextcloud" : "gpodder"
0300                 }
0301                 ColumnLayout {
0302                     Layout.fillWidth: true
0303                     Layout.fillHeight: true
0304                     Kirigami.Heading {
0305                         clip: true
0306                         level: 2
0307                         text: Sync.provider === Sync.GPodderNextcloud ? i18n("Sync with GPodder Nextcloud app") : i18n("Sync with gpodder.net service")
0308                     }
0309                     TextEdit {
0310                         Layout.fillWidth: true
0311                         readOnly: true
0312                         wrapMode: Text.WordWrap
0313                         textFormat: Text.RichText
0314                         onLinkActivated: (link) => {
0315                             Qt.openUrlExternally(link);
0316                         }
0317                         text: Sync.provider === Sync.GPodderNextcloud ?
0318                             i18nc("argument is a weblink", "Sync with a Nextcloud server that has the GPodder Sync app installed: %1.<br/>It is advised to manually create an app password for Kasts through the web interface and use those credentials." , "<a href=\"https://apps.nextcloud.com/apps/gpoddersync\">https://apps.nextcloud.com/apps/gpoddersync</a>") :
0319                             i18nc("argument is a weblink", "If you don't already have an account, you should first create one at %1", "<a href=\"https://gpodder.net\">https://gpodder.net</a>")
0320                         color: Kirigami.Theme.textColor
0321                     }
0322                 }
0323             }
0324             GridLayout {
0325                 width: parent.width
0326                 columns: 2
0327                 rowSpacing: Kirigami.Units.smallSpacing
0328                 columnSpacing: Kirigami.Units.smallSpacing
0329                 Controls.Label {
0330                     Layout.alignment: Qt.AlignRight
0331                     text: i18n("Username:")
0332                 }
0333                 Controls.TextField {
0334                     id: usernameField
0335                     Layout.fillWidth: true
0336                     text: Sync.username
0337                     Keys.onReturnPressed: syncLoginOverlay.accepted();
0338                     // focus: syncLoginOverlay.visible // disabled for now since it causes problem with virtual keyboard appearing at the same time as the overlay
0339                 }
0340                 Controls.Label {
0341                     Layout.alignment: Qt.AlignRight
0342                     text: i18n("Password:")
0343                 }
0344                 Controls.TextField {
0345                     id: passwordField
0346                     Layout.fillWidth: true
0347                     echoMode: TextInput.Password
0348                     text: Sync.password
0349                     Keys.onReturnPressed: syncLoginOverlay.accepted();
0350                 }
0351                 Controls.CheckBox {
0352                     id: customServerCheckBox
0353                     Layout.row: 2
0354                     Layout.column: 1
0355                     visible: Sync.provider === Sync.GPodderNet
0356                     checked: false
0357                     text: i18n("Use custom server")
0358                 }
0359                 Controls.Label {
0360                     visible: Sync.provider === Sync.GPodderNextcloud || customServerCheckBox.checked
0361                     Layout.alignment: Qt.AlignRight
0362                     text: i18n("Hostname:")
0363                 }
0364                 Controls.TextField {
0365                     visible: Sync.provider === Sync.GPodderNextcloud || customServerCheckBox.checked
0366                     id: hostnameField
0367                     Layout.fillWidth: true
0368                     placeholderText: Sync.provider === Sync.GPodderNet ? "https://gpodder.net" : "https://nextcloud.mydomain.org"
0369                     text: Sync.hostname
0370                     Keys.onReturnPressed: syncLoginOverlay.accepted();
0371                 }
0372             }
0373         }
0374     }
0375 
0376     Connections {
0377         target: Sync
0378         function onDeviceListReceived() {
0379             syncDeviceOverlay.open();
0380             syncDeviceOverlay.update();
0381         }
0382         function onLoginSucceeded() {
0383             if (Sync.provider === Sync.GPodderNextcloud) {
0384                 firstSyncOverlay.open();
0385             }
0386         }
0387     }
0388 
0389     Kirigami.Dialog {
0390         id: syncDeviceOverlay
0391         preferredWidth: Kirigami.Units.gridUnit * 25
0392         padding: Kirigami.Units.largeSpacing
0393 
0394         showCloseButton: true
0395 
0396         title: i18n("Sync Device Settings")
0397 
0398         Column {
0399             spacing: Kirigami.Units.largeSpacing * 2
0400             Kirigami.Heading {
0401                 level: 2
0402                 text: i18n("Create a new device")
0403             }
0404             GridLayout {
0405                 columns: 2
0406                 width: parent.width
0407                 Controls.Label {
0408                     text: i18n("Device Name:")
0409                 }
0410                 Controls.TextField {
0411                     id: deviceField
0412                     Layout.fillWidth: true
0413                     text: Sync.suggestedDevice
0414                     Keys.onReturnPressed: createDeviceButton.clicked();
0415                     // focus: syncDeviceOverlay.visible // disabled for now since it causes problem with virtual keyboard appearing at the same time as the overlay
0416                 }
0417                 Controls.Label {
0418                     text: i18n("Device Description:")
0419                 }
0420                 Controls.TextField {
0421                     id: deviceNameField
0422                     Layout.fillWidth: true
0423                     text: Sync.suggestedDeviceName
0424                     Keys.onReturnPressed: createDeviceButton.clicked();
0425                 }
0426                 Controls.Label {
0427                     text: i18n("Device Type:")
0428                 }
0429                 Controls.ComboBox {
0430                     id: deviceTypeField
0431                     textRole: "text"
0432                     valueRole: "value"
0433                     popup.z: 102 // popup has to go in front of OverlaySheet
0434                     model: [{"text": i18n("other"), "value": "other"},
0435                             {"text": i18n("desktop"), "value": "desktop"},
0436                             {"text": i18n("laptop"), "value": "laptop"},
0437                             {"text": i18n("server"), "value": "server"},
0438                             {"text": i18n("mobile"), "value": "mobile"}]
0439                 }
0440             }
0441             Controls.Button {
0442                 id: createDeviceButton
0443                 text: i18n("Create Device")
0444                 icon.name: "list-add"
0445                 onClicked: {
0446                     Sync.registerNewDevice(deviceField.text, deviceNameField.text, deviceTypeField.currentValue);
0447                     syncDeviceOverlay.close();
0448                 }
0449             }
0450             ListView {
0451                 id: deviceList
0452                 width: parent.width
0453                 height: contentItem.childrenRect.height
0454                 visible: deviceListModel.count !== 0
0455 
0456                 header: Kirigami.Heading {
0457                     topPadding: Kirigami.Units.gridUnit
0458                     bottomPadding: Kirigami.Units.largeSpacing
0459                     level: 2
0460                     text: i18n("or select an existing device")
0461                 }
0462                 model: ListModel {
0463                     id: deviceListModel
0464                 }
0465 
0466                 delegate: Delegates.RoundedItemDelegate {
0467                     text: model.device.caption
0468                     icon.name: model.device.type == "desktop" ? "computer" :
0469                         model.device.type == "laptop" ? "computer-laptop" :
0470                         model.device.type == "server" ? "network-server-database" :
0471                         model.device.type == "mobile" ? "smartphone" :
0472                         "emblem-music-symbolic"
0473                     onClicked: {
0474                         syncDeviceOverlay.close();
0475                         Sync.device = model.device.id;
0476                         Sync.deviceName = model.device.caption;
0477                         Sync.syncEnabled = true;
0478                         syncGroupOverlay.open();
0479                     }
0480                 }
0481             }
0482         }
0483 
0484         function update() {
0485             deviceListModel.clear();
0486             for (var index in Sync.deviceList) {
0487                 deviceListModel.append({"device": Sync.deviceList[index]});
0488             }
0489         }
0490     }
0491 
0492     Connections {
0493         target: Sync
0494         function onDeviceCreated() {
0495             syncGroupOverlay.open();
0496         }
0497     }
0498 
0499     Kirigami.Dialog {
0500         id: syncGroupOverlay
0501         preferredWidth: Kirigami.Units.gridUnit * 25
0502         padding: Kirigami.Units.largeSpacing
0503 
0504         showCloseButton: true
0505         standardButtons: Controls.DialogButtonBox.Ok | Controls.DialogButtonBox.Cancel
0506         closePolicy: Kirigami.Dialog.CloseOnEscape | Kirigami.Dialog.CloseOnPressOutside
0507 
0508         title: i18n("Device Sync Settings")
0509 
0510         onAccepted: {
0511             Sync.linkUpAllDevices();
0512             syncGroupOverlay.close();
0513         }
0514         onRejected: {
0515             syncGroupOverlay.close();
0516         }
0517 
0518         RowLayout {
0519             spacing: Kirigami.Units.largeSpacing
0520             Kirigami.Icon {
0521                 Layout.preferredHeight: Kirigami.Units.gridUnit * 4
0522                 Layout.preferredWidth: Kirigami.Units.gridUnit * 4
0523                 source: "gpodder"
0524             }
0525             TextEdit {
0526                 Layout.fillWidth: true
0527                 Layout.fillHeight: true
0528                 readOnly: true
0529                 wrapMode: Text.WordWrap
0530                 text: i18n("Should all podcast subscriptions on this gpodder.net account be synced across all devices?\nIf you don't know what this means, you should probably select \"Ok\".")
0531                 color: Kirigami.Theme.textColor
0532                 Keys.onReturnPressed: accepted();
0533             }
0534         }
0535 
0536         onVisibleChanged: {
0537             if (!visible) {
0538                 firstSyncOverlay.open();
0539             }
0540         }
0541     }
0542 
0543     Kirigami.Dialog {
0544         id: firstSyncOverlay
0545         preferredWidth: Kirigami.Units.gridUnit * 16
0546         padding: Kirigami.Units.largeSpacing
0547 
0548         showCloseButton: true
0549         standardButtons: Controls.DialogButtonBox.Ok | Controls.DialogButtonBox.Cancel
0550         closePolicy: Kirigami.Dialog.CloseOnEscape | Kirigami.Dialog.CloseOnPressOutside
0551 
0552         title: i18n("Sync Now?")
0553 
0554         onAccepted: {
0555             firstSyncOverlay.close();
0556             Sync.doRegularSync();
0557         }
0558         onRejected: firstSyncOverlay.close();
0559 
0560         RowLayout {
0561             spacing: Kirigami.Units.largeSpacing
0562             Kirigami.Icon {
0563                 Layout.preferredHeight: Kirigami.Units.gridUnit * 4
0564                 Layout.preferredWidth: Kirigami.Units.gridUnit * 4
0565                 source: Sync.provider === Sync.GPodderNextcloud ? "kaccounts-nextcloud" : "gpodder"
0566             }
0567             TextEdit {
0568                 Layout.fillWidth: true
0569                 Layout.fillHeight: true
0570                 readOnly: true
0571                 wrapMode: Text.WordWrap
0572                 text: i18n("Perform a first sync now?")
0573                 color: Kirigami.Theme.textColor
0574                 Keys.onReturnPressed: accepted();
0575             }
0576         }
0577     }
0578 }