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 }