Warning, /multimedia/kasts/src/qml/Main.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-2022 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 import QtQuick.Effects
0012 import QtCore
0013 
0014 import org.kde.kirigami as Kirigami
0015 
0016 import org.kde.kasts
0017 import org.kde.kasts.settings
0018 
0019 import "Desktop"
0020 import "Mobile"
0021 
0022 
0023 Kirigami.ApplicationWindow {
0024     id: kastsMainWindow
0025     title: i18n("Kasts")
0026 
0027     width: Kirigami.Settings.isMobile ? 360 : 800
0028     height: Kirigami.Settings.isMobile ? 660 : 600
0029 
0030     pageStack.clip: true
0031     pageStack.popHiddenPages: true
0032     pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar;
0033     pageStack.globalToolBar.showNavigationButtons: Kirigami.ApplicationHeaderStyle.ShowBackButton;
0034 
0035     // only have a single page visible at any time
0036     pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn
0037 
0038     minimumWidth: Kirigami.Units.gridUnit * 17
0039     minimumHeight: Kirigami.Units.gridUnit * 12
0040 
0041     property var miniplayerSize: Kirigami.Units.gridUnit * 3 + Kirigami.Units.gridUnit / 6
0042     property int bottomMessageSpacing: {
0043         if (Kirigami.Settings.isMobile) {
0044             return Kirigami.Units.largeSpacing + ( AudioManager.entry ? ( footerLoader.item.contentY == 0 ? miniplayerSize : 0 ) : 0 )
0045         } else {
0046             return Kirigami.Units.largeSpacing;
0047         }
0048     }
0049     property var lastFeed: ""
0050     property string currentPage: ""
0051     property int feedSorting: FeedsProxyModel.UnreadDescending
0052 
0053     property bool isWidescreen: kastsMainWindow.width > kastsMainWindow.height
0054 
0055     function getPage(page) {
0056         switch (page) {
0057             case "QueuePage": return "qrc:/qt/qml/org/kde/kasts/qml/QueuePage.qml";
0058             case "EpisodeListPage": return "qrc:/qt/qml/org/kde/kasts/qml/EpisodeListPage.qml";
0059             case "DiscoverPage": return "qrc:/qt/qml/org/kde/kasts/qml/DiscoverPage.qml";
0060             case "FeedListPage": return "qrc:/qt/qml/org/kde/kasts/qml/FeedListPage.qml";
0061             case "DownloadListPage": return "qrc:/qt/qml/org/kde/kasts/qml/DownloadListPage.qml";
0062             case "SettingsPage": return "qrc:/qt/qml/org/kde/kasts/qml/Settings/SettingsPage.qml";
0063             default: {
0064                 currentPage = "FeedListPage";
0065                 return "qrc:/qt/qml/org/kde/kasts/qml/FeedListPage.qml";
0066             }
0067         }
0068     }
0069     function pushPage(page) {
0070         if (page === "SettingsPage") {
0071             pageStack.layers.clear()
0072             pageStack.pushDialogLayer("qrc:/qt/qml/org/kde/kasts/qml/Settings/SettingsPage.qml", {}, {
0073                 title: i18n("Settings")
0074             })
0075         } else {
0076             pageStack.clear();
0077             pageStack.layers.clear();
0078             pageStack.push(getPage(page));
0079             currentPage = page;
0080         }
0081     }
0082 
0083     Settings {
0084         id: settings
0085 
0086         property alias x: kastsMainWindow.x
0087         property alias y: kastsMainWindow.y
0088         property var mobileWidth
0089         property var mobileHeight
0090         property var desktopWidth
0091         property var desktopHeight
0092         property int headerSize: Kirigami.Units.gridUnit * 5
0093         property alias lastOpenedPage: kastsMainWindow.currentPage
0094         property alias feedSorting: kastsMainWindow.feedSorting
0095     }
0096 
0097     function saveWindowLayout() {
0098         if (Kirigami.Settings.isMobile) {
0099             settings.mobileWidth = kastsMainWindow.width;
0100             settings.mobileHeight = kastsMainWindow.height;
0101         } else {
0102             settings.desktopWidth = kastsMainWindow.width;
0103             settings.desktopHeight = kastsMainWindow.height;
0104         }
0105     }
0106 
0107     function restoreWindowLayout() {
0108         if (Kirigami.Settings.isMobile) {
0109             if (settings.mobileWidth) kastsMainWindow.width = settings.mobileWidth;
0110             if (settings.mobileHeight) kastsMainWindow.height = settings.mobileHeight;
0111         } else {
0112             if (settings.desktopWidth) kastsMainWindow.width = settings.desktopWidth;
0113             if (settings.desktopHeight) kastsMainWindow.height = settings.desktopHeight;
0114         }
0115     }
0116 
0117     Component.onDestruction: {
0118         saveWindowLayout();
0119     }
0120 
0121     Component.onCompleted: {
0122         restoreWindowLayout();
0123         pageStack.initialPage = getPage(currentPage);
0124 
0125         // Delete played enclosures if set in settings
0126         if (SettingsManager.autoDeleteOnPlayed == 2) {
0127             DataManager.deletePlayedEnclosures();
0128         }
0129 
0130         // Refresh feeds on startup if allowed
0131         // NOTE: refresh+sync on startup is handled in Sync and not here, since it
0132         // requires credentials to be loaded before starting a refresh+sync
0133         if (NetworkConnectionManager.feedUpdatesAllowed) {
0134             if (SettingsManager.refreshOnStartup && !(SettingsManager.syncEnabled && SettingsManager.syncWhenUpdatingFeeds)) {
0135                 Fetcher.fetchAll();
0136             }
0137         }
0138     }
0139 
0140     property bool showGlobalDrawer: !Kirigami.Settings.isMobile || kastsMainWindow.isWidescreen
0141 
0142     globalDrawer: showGlobalDrawer ? myGlobalDrawer : null
0143 
0144     property Kirigami.OverlayDrawer myGlobalDrawer: KastsGlobalDrawer {
0145 
0146     }
0147 
0148     // Implement slots for MPRIS2 signals
0149     Connections {
0150         target: AudioManager
0151         function onRaiseWindowRequested() {
0152             kastsMainWindow.visible = true;
0153             kastsMainWindow.show();
0154             kastsMainWindow.raise();
0155             kastsMainWindow.requestActivate();
0156         }
0157     }
0158     Connections {
0159         target: AudioManager
0160         function onQuitRequested() {
0161             kastsMainWindow.close();
0162         }
0163     }
0164 
0165     header: Loader {
0166         id: headerLoader
0167         active: !Kirigami.Settings.isMobile
0168         visible: active
0169 
0170         sourceComponent: HeaderBar { focus: true }
0171     }
0172 
0173     // create space at the bottom to show miniplayer without it hiding stuff
0174     // underneath
0175     pageStack.anchors.bottomMargin: (AudioManager.entry && Kirigami.Settings.isMobile) ? miniplayerSize + 1 : 0
0176 
0177     Loader {
0178         id: footerLoader
0179 
0180         anchors.fill: parent
0181         active: AudioManager.entry && Kirigami.Settings.isMobile
0182         visible: active
0183         z: (!item || item.contentY === 0) ? -1 : 999
0184         sourceComponent: FooterBar {
0185             contentHeight: kastsMainWindow.height * 2
0186             focus: true
0187             contentToPlayerSpacing: footer.active ? footer.item.height + 1 : 0
0188         }
0189     }
0190 
0191     Loader {
0192         id: footerShadowLoader
0193         active: footer.active && !footerLoader.active
0194         anchors.fill: footer
0195 
0196         sourceComponent: MultiEffect {
0197             source: bottomToolbarLoader
0198             shadowEnabled: true
0199             shadowScale: 1.1
0200             blurMax: 10
0201             shadowColor: Qt.rgba(0.0, 0.0, 0.0, 0.1)
0202         }
0203     }
0204 
0205     footer: Loader {
0206         id: bottomToolbarLoader
0207         visible: active
0208         height: visible ? implicitHeight : 0
0209         active: Kirigami.Settings.isMobile && !kastsMainWindow.isWidescreen
0210         sourceComponent: BottomToolbar {
0211             transparentBackground: footerLoader.active
0212             opacity: (!footerLoader.item || footerLoader.item.contentY === 0) ? 1 : 0
0213             Behavior on opacity {
0214                 NumberAnimation { duration: Kirigami.Units.shortDuration }
0215             }
0216         }
0217     }
0218 
0219     // Notification that shows the progress of feed updates
0220     // It mimicks the behaviour of an InlineMessage, because InlineMessage does
0221     // not allow to add a BusyIndicator
0222     UpdateNotification {
0223         id: updateNotification
0224         text: i18ncp("Number of Updated Podcasts",
0225                      "Updated %2 of %1 Podcast",
0226                      "Updated %2 of %1 Podcasts",
0227                      Fetcher.updateTotal,
0228                      Fetcher.updateProgress)
0229 
0230         showAbortButton: true
0231 
0232         function abortAction() {
0233             Fetcher.cancelFetching();
0234         }
0235 
0236         Connections {
0237             target: Fetcher
0238             function onUpdatingChanged() {
0239                 if (Fetcher.updating) {
0240                     updateNotification.open();
0241                 } else {
0242                     updateNotification.close();
0243                 }
0244             }
0245         }
0246     }
0247 
0248     // Notification to show progress of copying enclosure and images to new location
0249     UpdateNotification {
0250         id: moveStorageNotification
0251         text: i18ncp("Number of Moved Files",
0252                      "Moved %2 of %1 File",
0253                      "Moved %2 of %1 Files",
0254                      StorageManager.storageMoveTotal,
0255                      StorageManager.storageMoveProgress)
0256         showAbortButton: true
0257 
0258         function abortAction() {
0259             StorageManager.cancelStorageMove();
0260         }
0261 
0262         Connections {
0263             target: StorageManager
0264             function onStorageMoveStarted() {
0265                 moveStorageNotification.open()
0266             }
0267             function onStorageMoveFinished() {
0268                 moveStorageNotification.close()
0269             }
0270         }
0271     }
0272 
0273     // Notification that shows the progress of feed and episode syncing
0274     UpdateNotification {
0275         id: updateSyncNotification
0276         text: Sync.syncProgressText
0277         showAbortButton: true
0278 
0279         function abortAction() {
0280             Sync.abortSync();
0281         }
0282 
0283         Connections {
0284             target: Sync
0285             function onSyncProgressChanged() {
0286                 if (Sync.syncStatus != SyncUtils.NoSync && Sync.syncProgress === 0) {
0287                     updateSyncNotification.open();
0288                 } else if (Sync.syncStatus === SyncUtils.NoSync) {
0289                     updateSyncNotification.close();
0290                 }
0291             }
0292         }
0293     }
0294 
0295 
0296     // This InlineMessage is used for displaying error messages
0297     ErrorNotification {
0298         id: errorNotification
0299     }
0300 
0301     // overlay with log of all errors that have happened
0302     ErrorListOverlay {
0303         id: errorOverlay
0304     }
0305 
0306     // This item can be used to trigger an update of all feeds; it will open an
0307     // overlay with options in case the operation is not allowed by the settings
0308     ConnectionCheckAction {
0309         id: updateAllFeeds
0310     }
0311 
0312     // Overlay with options what to do when metered downloads are not allowed
0313     ConnectionCheckAction {
0314         id: downloadOverlay
0315 
0316         headingText: i18nc("@info:status", "Podcast downloads are currently not allowed on metered connections")
0317         condition: NetworkConnectionManager.episodeDownloadsAllowed
0318         property var entry: undefined
0319         property var selection: undefined
0320 
0321         function action() {
0322             if (selection) {
0323                 DataManager.bulkDownloadEnclosuresByIndex(selection);
0324             } else if (entry) {
0325                 entry.queueStatus = true;
0326                 entry.enclosure.download();
0327             }
0328             selection = undefined;
0329             entry = undefined;
0330         }
0331 
0332         function allowOnceAction() {
0333             SettingsManager.allowMeteredEpisodeDownloads = true;
0334             action();
0335             SettingsManager.allowMeteredEpisodeDownloads = false;
0336         }
0337 
0338         function alwaysAllowAction() {
0339             SettingsManager.allowMeteredEpisodeDownloads = true;
0340             SettingsManager.save();
0341             action();
0342         }
0343     }
0344 
0345     SleepTimerDialog {
0346         id: sleepTimerDialog
0347     }
0348 
0349     Connections {
0350         target: Sync
0351         function onPasswordInputRequired() {
0352             syncPasswordOverlay.open();
0353         }
0354     }
0355 
0356     SyncPasswordOverlay {
0357         id: syncPasswordOverlay
0358     }
0359 
0360     Loader {
0361         id: fullScreenImageLoader
0362         active: false
0363         visible: active
0364     }
0365 
0366     //Global Shortcuts
0367     Shortcut {
0368         sequence:  "space"
0369         enabled: AudioManager.canPlay
0370         onActivated: AudioManager.playPause()
0371     }
0372     Shortcut {
0373         sequence:  "n"
0374         enabled: AudioManager.canGoNext
0375         onActivated: AudioManager.next()
0376     }
0377 
0378     // Systray implementation
0379     Connections {
0380         target: kastsMainWindow
0381 
0382         function onClosing(close) {
0383             if (SystrayIcon.available && SettingsManager.showTrayIcon && SettingsManager.minimizeToTray) {
0384                 close.accepted = false;
0385                 kastsMainWindow.hide();
0386             } else {
0387                 close.accepted = true;
0388                 Qt.quit();
0389             }
0390         }
0391     }
0392 
0393     Connections {
0394         target: SystrayIcon
0395 
0396         function onRaiseWindow() {
0397             if (kastsMainWindow.visible) {
0398                 kastsMainWindow.visible = false;
0399                 kastsMainWindow.hide();
0400             } else {
0401                 kastsMainWindow.visible = true;
0402                 kastsMainWindow.show();
0403                 kastsMainWindow.raise();
0404                 kastsMainWindow.requestActivate();
0405             }
0406         }
0407     }
0408 }