Move page actions to bar, and consolidate podcast list and info pages
This commit is contained in:
parent
9860c8b9e5
commit
3a6446dea5
@ -1,122 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
|
||||||
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.14 as Controls
|
|
||||||
import QtQuick.Layouts 1.14
|
|
||||||
import QtGraphicalEffects 1.15
|
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.kasts 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
id: page
|
|
||||||
|
|
||||||
property var feed
|
|
||||||
|
|
||||||
title: i18n("Episode List")
|
|
||||||
supportsRefreshing: true
|
|
||||||
|
|
||||||
onRefreshingChanged: {
|
|
||||||
if(refreshing) {
|
|
||||||
updateFeed.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overlay dialog box showing options what to do on metered connections
|
|
||||||
ConnectionCheckAction {
|
|
||||||
id: updateFeed
|
|
||||||
|
|
||||||
function action() {
|
|
||||||
feed.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
function abortAction() {
|
|
||||||
page.refreshing = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that this feed is also showing as "refreshing" on FeedListPage
|
|
||||||
Connections {
|
|
||||||
target: feed
|
|
||||||
function onRefreshingChanged(refreshing) {
|
|
||||||
if(!refreshing)
|
|
||||||
page.refreshing = refreshing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.main: Kirigami.Action {
|
|
||||||
iconName: "view-refresh"
|
|
||||||
text: i18n("Refresh Podcast")
|
|
||||||
onTriggered: page.refreshing = true
|
|
||||||
}
|
|
||||||
|
|
||||||
contextualActions: [
|
|
||||||
Kirigami.Action {
|
|
||||||
iconName: "help-about-symbolic"
|
|
||||||
text: i18n("Podcast Details")
|
|
||||||
onTriggered: {
|
|
||||||
while(pageStack.depth > 2)
|
|
||||||
pageStack.pop()
|
|
||||||
pageStack.push("qrc:/FeedDetailsPage.qml", {"feed": feed})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// add the default actions through onCompleted to add them to the ones
|
|
||||||
// defined above
|
|
||||||
Component.onCompleted: {
|
|
||||||
for (var i in entryList.defaultActionList) {
|
|
||||||
contextualActions.push(entryList.defaultActionList[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
|
||||||
visible: entryList.count === 0
|
|
||||||
|
|
||||||
width: Kirigami.Units.gridUnit * 20
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
text: feed.errorId === 0 ? i18n("No Episodes Available") : i18n("Error (%1): %2", feed.errorId, feed.errorString)
|
|
||||||
icon.name: feed.errorId === 0 ? "" : "data-error"
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: entryListDelegate
|
|
||||||
GenericEntryDelegate {
|
|
||||||
listView: entryList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GenericEntryListView {
|
|
||||||
id: entryList
|
|
||||||
visible: count !== 0
|
|
||||||
reuseItems: true
|
|
||||||
|
|
||||||
model: page.feed.entries
|
|
||||||
delegate: entryListDelegate
|
|
||||||
|
|
||||||
// OverlayHeader looks nicer, but seems completely broken when flicking the list
|
|
||||||
// headerPositioning: ListView.OverlayHeader
|
|
||||||
header: GenericHeader {
|
|
||||||
id: headerImage
|
|
||||||
|
|
||||||
image: feed.cachedImage
|
|
||||||
title: feed.name
|
|
||||||
subtitle: page.feed.authors.length === 0 ? "" : i18nc("by <author(s)>", "by %1", page.feed.authors[0].name)
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
while(pageStack.depth > 2)
|
|
||||||
pageStack.pop()
|
|
||||||
pageStack.push("qrc:/FeedDetailsPage.qml", {"feed": feed})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,6 +23,13 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
padding: 0 // needed to get the inline header to fill the page
|
padding: 0 // needed to get the inline header to fill the page
|
||||||
|
|
||||||
|
function openPodcast() {
|
||||||
|
pushPage("FeedListPage")
|
||||||
|
SettingsManager.lastOpenedPage = "FeedListPage" // for persistency
|
||||||
|
lastFeed = entry.feed.url;
|
||||||
|
pageStack.push("qrc:/FeedDetailsPage.qml", {"feed": entry.feed});
|
||||||
|
}
|
||||||
|
|
||||||
// This function is needed to close the EntryPage if it is opened over the
|
// This function is needed to close the EntryPage if it is opened over the
|
||||||
// QueuePage when the episode is removed from the queue (e.g. when the
|
// QueuePage when the episode is removed from the queue (e.g. when the
|
||||||
// episode finishes).
|
// episode finishes).
|
||||||
@ -65,30 +72,149 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
GenericHeader {
|
GenericHeader {
|
||||||
id: infoHeader
|
id: infoHeader
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
image: entry.cachedImage
|
image: entry.cachedImage
|
||||||
title: entry.title
|
title: entry.title
|
||||||
subtitle: entry.feed.name
|
subtitle: entry.feed.name
|
||||||
|
clickable: true
|
||||||
|
|
||||||
|
onClicked: page.openPodcast()
|
||||||
|
}
|
||||||
|
|
||||||
|
// header actions
|
||||||
|
Controls.Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.smallSpacing
|
||||||
|
topPadding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.alternateBackgroundColor
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Kirigami.ActionToolBar {
|
||||||
|
alignment: Qt.AlignLeft
|
||||||
|
background: Item {}
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
text: !entry.enclosure ? i18n("Open in Browser") :
|
||||||
|
(entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) ? i18n("Download") :
|
||||||
|
entry.enclosure.status === Enclosure.Downloading ? i18n("Cancel Download") :
|
||||||
|
!entry.queueStatus ? i18n("Delete Download") :
|
||||||
|
(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState) ? i18n("Pause") :
|
||||||
|
i18n("Play")
|
||||||
|
icon.name: !entry.enclosure ? "globe" :
|
||||||
|
(entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) ? "download" :
|
||||||
|
entry.enclosure.status === Enclosure.Downloading ? "edit-delete-remove" :
|
||||||
|
!entry.queueStatus ? "delete" :
|
||||||
|
(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState) ? "media-playback-pause" :
|
||||||
|
"media-playback-start"
|
||||||
|
onTriggered: {
|
||||||
|
if (!entry.enclosure) {
|
||||||
|
Qt.openUrlExternally(entry.link)
|
||||||
|
} else if (entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) {
|
||||||
|
downloadOverlay.entry = entry;
|
||||||
|
downloadOverlay.run();
|
||||||
|
} else if (entry.enclosure.status === Enclosure.Downloading) {
|
||||||
|
entry.enclosure.cancelDownload()
|
||||||
|
} else if (!entry.queueStatus) {
|
||||||
|
entry.enclosure.deleteFile()
|
||||||
|
} else {
|
||||||
|
if(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState) {
|
||||||
|
AudioManager.pause()
|
||||||
|
} else {
|
||||||
|
AudioManager.entry = entry
|
||||||
|
AudioManager.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: !entry.queueStatus ? i18n("Add to Queue") : i18n("Remove from Queue")
|
||||||
|
icon.name: !entry.queueStatus ? "media-playlist-append" : "list-remove"
|
||||||
|
visible: entry.enclosure || entry.queueStatus
|
||||||
|
onTriggered: {
|
||||||
|
if(!entry.queueStatus) {
|
||||||
|
entry.queueStatus = true
|
||||||
|
} else {
|
||||||
|
// first change to next track if this one is playing
|
||||||
|
if (entry.hasEnclosure && entry === AudioManager.entry) {
|
||||||
|
AudioManager.next()
|
||||||
|
}
|
||||||
|
entry.queueStatus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Delete Download")
|
||||||
|
icon.name: "delete"
|
||||||
|
onTriggered: entry.enclosure.deleteFile();
|
||||||
|
visible: entry.enclosure && ((entry.enclosure.status === Enclosure.Downloaded && entry.queueStatus) || entry.enclosure.status === Enclosure.PartiallyDownloaded)
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Reset Play Position")
|
||||||
|
visible: entry.enclosure && entry.enclosure.playPosition > 1000
|
||||||
|
onTriggered: entry.enclosure.playPosition = 0
|
||||||
|
displayHint: Kirigami.DisplayHint.AlwaysHide
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: entry.read ? i18n("Mark as Unplayed") : i18n("Mark as Played")
|
||||||
|
displayHint: Kirigami.DisplayHint.AlwaysHide
|
||||||
|
onTriggered: {
|
||||||
|
entry.read = !entry.read
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: entry.new ? i18n("Remove \"New\" Label") : i18n("Label as \"New\"")
|
||||||
|
displayHint: Kirigami.DisplayHint.AlwaysHide
|
||||||
|
onTriggered: {
|
||||||
|
entry.new = !entry.new
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Open Podcast")
|
||||||
|
displayHint: Kirigami.DisplayHint.AlwaysHide
|
||||||
|
onTriggered: page.openPodcast()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
id: textLabel
|
id: textLabel
|
||||||
Layout.margins: Kirigami.Units.gridUnit
|
Layout.topMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.bottomMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
readOnly: true
|
readOnly: true
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
text: page.entry.content
|
text: page.entry.content
|
||||||
baseUrl: page.entry.baseUrl
|
baseUrl: page.entry.baseUrl
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
|
||||||
onWidthChanged: { text = entry.adjustedContent(width, font.pixelSize) }
|
|
||||||
font.pointSize: SettingsManager && !(SettingsManager.articleFontUseSystem) ? SettingsManager.articleFontSize : Kirigami.Theme.defaultFont.pointSize
|
font.pointSize: SettingsManager && !(SettingsManager.articleFontUseSystem) ? SettingsManager.articleFontSize : Kirigami.Theme.defaultFont.pointSize
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
|
|
||||||
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
onWidthChanged: { text = entry.adjustedContent(width, font.pixelSize) }
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
visible: count !== 0
|
visible: count !== 0
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -105,19 +231,26 @@ Kirigami.ScrollablePage {
|
|||||||
entry: page.entry
|
entry: page.entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RowLayout {
|
|
||||||
|
Controls.Button {
|
||||||
Layout.leftMargin: Kirigami.Units.gridUnit
|
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||||
Layout.rightMargin: Kirigami.Units.gridUnit
|
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||||
Layout.bottomMargin: Kirigami.Units.gridUnit
|
Layout.bottomMargin: Kirigami.Units.gridUnit
|
||||||
visible: entry.hasEnclosure
|
visible: entry.hasEnclosure
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
TextEdit {
|
text: i18n("Copy Episode Download URL")
|
||||||
readOnly: true
|
height: enclosureUrl.height
|
||||||
textFormat:TextEdit.RichText
|
width: enclosureUrl.height
|
||||||
text: i18n("Episode Download URL:")
|
icon.name: "edit-copy"
|
||||||
wrapMode: TextEdit.Wrap
|
|
||||||
color: Kirigami.Theme.textColor
|
onClicked: {
|
||||||
|
applicationWindow().showPassiveNotification(i18n("Link copied"));
|
||||||
|
enclosureUrl.selectAll();
|
||||||
|
enclosureUrl.copy();
|
||||||
|
enclosureUrl.deselect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy url from this invisible textedit
|
||||||
TextEdit {
|
TextEdit {
|
||||||
id: enclosureUrl
|
id: enclosureUrl
|
||||||
visible: false
|
visible: false
|
||||||
@ -126,103 +259,6 @@ Kirigami.ScrollablePage {
|
|||||||
text: entry.hasEnclosure ? entry.enclosure.url : ""
|
text: entry.hasEnclosure ? entry.enclosure.url : ""
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
}
|
}
|
||||||
Controls.Button {
|
|
||||||
height: enclosureUrl.height
|
|
||||||
width: enclosureUrl.height
|
|
||||||
icon.name: "edit-copy"
|
|
||||||
onClicked: {
|
|
||||||
enclosureUrl.selectAll();
|
|
||||||
enclosureUrl.copy();
|
|
||||||
enclosureUrl.deselect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.main: Kirigami.Action {
|
|
||||||
text: !entry.enclosure ? i18n("Open in Browser") :
|
|
||||||
(entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) ? i18n("Download") :
|
|
||||||
entry.enclosure.status === Enclosure.Downloading ? i18n("Cancel Download") :
|
|
||||||
!entry.queueStatus ? i18n("Delete Download") :
|
|
||||||
(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState) ? i18n("Pause") :
|
|
||||||
i18n("Play")
|
|
||||||
icon.name: !entry.enclosure ? "globe" :
|
|
||||||
(entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) ? "download" :
|
|
||||||
entry.enclosure.status === Enclosure.Downloading ? "edit-delete-remove" :
|
|
||||||
!entry.queueStatus ? "delete" :
|
|
||||||
(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState) ? "media-playback-pause" :
|
|
||||||
"media-playback-start"
|
|
||||||
onTriggered: {
|
|
||||||
if (!entry.enclosure) {
|
|
||||||
Qt.openUrlExternally(entry.link)
|
|
||||||
} else if (entry.enclosure.status === Enclosure.Downloadable || entry.enclosure.status === Enclosure.PartiallyDownloaded) {
|
|
||||||
downloadOverlay.entry = entry;
|
|
||||||
downloadOverlay.run();
|
|
||||||
} else if (entry.enclosure.status === Enclosure.Downloading) {
|
|
||||||
entry.enclosure.cancelDownload()
|
|
||||||
} else if (!entry.queueStatus) {
|
|
||||||
entry.enclosure.deleteFile()
|
|
||||||
} else {
|
|
||||||
if(AudioManager.entry === entry && AudioManager.playbackState === Audio.PlayingState) {
|
|
||||||
AudioManager.pause()
|
|
||||||
} else {
|
|
||||||
AudioManager.entry = entry
|
|
||||||
AudioManager.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.left: Kirigami.Action {
|
|
||||||
text: !entry.queueStatus ? i18n("Add to Queue") : i18n("Remove from Queue")
|
|
||||||
icon.name: !entry.queueStatus ? "media-playlist-append" : "list-remove"
|
|
||||||
visible: entry.enclosure || entry.queueStatus
|
|
||||||
onTriggered: {
|
|
||||||
if(!entry.queueStatus) {
|
|
||||||
entry.queueStatus = true
|
|
||||||
} else {
|
|
||||||
// first change to next track if this one is playing
|
|
||||||
if (entry.hasEnclosure && entry === AudioManager.entry) {
|
|
||||||
AudioManager.next()
|
|
||||||
}
|
|
||||||
entry.queueStatus = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.right: Kirigami.Action {
|
|
||||||
text: i18n("Delete Download")
|
|
||||||
icon.name: "delete"
|
|
||||||
onTriggered: entry.enclosure.deleteFile();
|
|
||||||
visible: entry.enclosure && ((entry.enclosure.status === Enclosure.Downloaded && entry.queueStatus) || entry.enclosure.status === Enclosure.PartiallyDownloaded)
|
|
||||||
}
|
|
||||||
|
|
||||||
contextualActions: [
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18n("Reset Play Position")
|
|
||||||
visible: entry.enclosure && entry.enclosure.playPosition > 1000
|
|
||||||
onTriggered: entry.enclosure.playPosition = 0
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
text: entry.read ? i18n("Mark as Unplayed") : i18n("Mark as Played")
|
|
||||||
onTriggered: {
|
|
||||||
entry.read = !entry.read
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
text: entry.new ? i18n("Remove \"New\" Label") : i18n("Label as \"New\"")
|
|
||||||
onTriggered: {
|
|
||||||
entry.new = !entry.new
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18n("Open Podcast")
|
|
||||||
onTriggered: {
|
|
||||||
pushPage("FeedListPage")
|
|
||||||
SettingsManager.lastOpenedPage = "FeedListPage" // for persistency
|
|
||||||
lastFeed = entry.feed.url;
|
|
||||||
pageStack.push("qrc:/EntryListPage.qml", {"feed": entry.feed});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -20,145 +20,304 @@ Kirigami.ScrollablePage {
|
|||||||
property bool isSubscribed: true
|
property bool isSubscribed: true
|
||||||
|
|
||||||
property string author: isSubscribed ? (page.feed.authors.length === 0 ? "" : page.feed.authors[0].name) : feed.author
|
property string author: isSubscribed ? (page.feed.authors.length === 0 ? "" : page.feed.authors[0].name) : feed.author
|
||||||
|
property bool showMoreInfo: false
|
||||||
|
|
||||||
title: i18n("Podcast Details")
|
title: i18n("Podcast Details")
|
||||||
|
|
||||||
header: GenericHeader {
|
supportsRefreshing: true
|
||||||
id: headerImage
|
|
||||||
|
|
||||||
image: isSubscribed ? feed.cachedImage : feed.image
|
onRefreshingChanged: {
|
||||||
title: isSubscribed ? feed.name : feed.title
|
if (refreshing) {
|
||||||
subtitle: author !== "" ? i18nc("by <Author(s)>", "by %1", author) : ""
|
updateFeed.run()
|
||||||
Controls.Button {
|
|
||||||
text: enabled ? i18n("Subscribe") : i18n("Subscribed")
|
|
||||||
icon.name: "kt-add-feeds"
|
|
||||||
visible: !isSubscribed
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
anchors.topMargin: Kirigami.Units.largeSpacing
|
|
||||||
onClicked: {
|
|
||||||
DataManager.addFeed(feed.url)
|
|
||||||
}
|
|
||||||
enabled: !DataManager.feedExists(feed.url)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
// Overlay dialog box showing options what to do on metered connections
|
||||||
width: parent.width
|
ConnectionCheckAction {
|
||||||
TextEdit {
|
id: updateFeed
|
||||||
readOnly: true
|
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
function action() {
|
||||||
textFormat:TextEdit.RichText
|
feed.refresh()
|
||||||
text: feed.description
|
|
||||||
font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.2)
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
}
|
}
|
||||||
TextEdit {
|
|
||||||
readOnly: true
|
function abortAction() {
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
page.refreshing = false
|
||||||
textFormat:TextEdit.RichText
|
|
||||||
text: i18nc("by <Author(s)>", "by %1", author)
|
|
||||||
visible: author !== ""
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
}
|
}
|
||||||
Item {
|
}
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: Math.max(feedUrlLayout.height, feedUrlCopyButton.width)
|
// Make sure that this feed is also showing as "refreshing" on FeedListPage
|
||||||
RowLayout {
|
Connections {
|
||||||
id: feedUrlLayout
|
target: feed
|
||||||
anchors.left: parent.left
|
function onRefreshingChanged(refreshing) {
|
||||||
anchors.right: feedUrlCopyButton.left
|
if(!refreshing)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
page.refreshing = refreshing
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
TextEdit {
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
readOnly: true
|
|
||||||
textFormat:TextEdit.RichText
|
|
||||||
text: i18n("Podcast URL:")
|
|
||||||
wrapMode: TextEdit.Wrap
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
}
|
|
||||||
TextEdit {
|
|
||||||
id: feedUrl
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
readOnly: true
|
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
|
||||||
textFormat:TextEdit.RichText
|
|
||||||
text: "<a href='%1'>%1</a>".arg(feed.url)
|
|
||||||
wrapMode: TextEdit.Wrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Controls.Button {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
|
||||||
id: feedUrlCopyButton
|
|
||||||
icon.name: "edit-copy"
|
|
||||||
onClicked: {
|
|
||||||
feedUrl.selectAll();
|
|
||||||
feedUrl.copy();
|
|
||||||
feedUrl.deselect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
RowLayout {
|
}
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
TextEdit {
|
// add the default actions through onCompleted to add them to the ones
|
||||||
Layout.alignment: Qt.AlignTop
|
// defined above
|
||||||
readOnly: true
|
Component.onCompleted: {
|
||||||
textFormat:TextEdit.RichText
|
for (var i in entryList.defaultActionList) {
|
||||||
text: i18n("Weblink:")
|
contextualActions.push(entryList.defaultActionList[i]);
|
||||||
wrapMode: TextEdit.Wrap
|
}
|
||||||
color: Kirigami.Theme.textColor
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: entryListDelegate
|
||||||
|
GenericEntryDelegate {
|
||||||
|
listView: entryList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: emptyListModel
|
||||||
|
}
|
||||||
|
|
||||||
|
GenericEntryListView {
|
||||||
|
id: entryList
|
||||||
|
visible: true
|
||||||
|
reuseItems: true
|
||||||
|
currentIndex: -1
|
||||||
|
|
||||||
|
model: page.feed.entries ? page.feed.entries : emptyListModel
|
||||||
|
delegate: entryListDelegate
|
||||||
|
|
||||||
|
// OverlayHeader looks nicer, but seems completely broken when flicking the list
|
||||||
|
//headerPositioning: ListView.OverlayHeader
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
id: headerColumn
|
||||||
|
height: (isSubscribed && entryList.count > 0) ? implicitHeight : entryList.height
|
||||||
|
width: entryList.width
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
property real headerOverlayProgress: Math.min(1, Math.abs(entryList.contentY) / headerColumn.height)
|
||||||
|
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
|
|
||||||
|
GenericHeader {
|
||||||
|
id: headerImage
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
image: isSubscribed ? feed.cachedImage : feed.image
|
||||||
|
title: isSubscribed ? feed.name : feed.title
|
||||||
|
subtitle: (!page.feed.authors || page.feed.authors.length === 0) ? "" : i18nc("by <author(s)>", "by %1", page.feed.authors[0].name)
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEdit {
|
// header actions
|
||||||
readOnly: true
|
Controls.Control {
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
|
||||||
textFormat:TextEdit.RichText
|
|
||||||
text: "<a href='%1'>%1</a>".arg(feed.link)
|
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.smallSpacing
|
||||||
|
topPadding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.alternateBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Kirigami.ActionToolBar {
|
||||||
|
alignment: Qt.AlignLeft
|
||||||
|
background: Item {}
|
||||||
|
|
||||||
|
// HACK: ActionToolBar loads buttons dynamically, and so the height calculation
|
||||||
|
// changes the position
|
||||||
|
onHeightChanged: entryList.contentY = entryList.originY
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
visible: isSubscribed
|
||||||
|
iconName: "view-refresh"
|
||||||
|
text: i18n("Refresh Podcast")
|
||||||
|
onTriggered: page.refreshing = true
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
iconName: "kt-add-feeds"
|
||||||
|
text: enabled ? i18n("Subscribe") : i18n("Subscribed")
|
||||||
|
enabled: !DataManager.feedExists(feed.url)
|
||||||
|
visible: !isSubscribed
|
||||||
|
onTriggered: DataManager.addFeed(feed.url)
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
iconName: "help-about-symbolic"
|
||||||
|
text: i18n("Show Details")
|
||||||
|
checkable: true
|
||||||
|
onCheckedChanged: {
|
||||||
|
showMoreInfo = checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
// podcast description
|
||||||
|
Controls.Control {
|
||||||
|
Layout.fillHeight: !isSubscribed
|
||||||
|
Layout.fillWidth: true
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
// HACK: opening more info changes the position of the header
|
||||||
|
onHeightChanged: entryList.contentY = entryList.originY
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Controls.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
text: feed.description
|
||||||
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
lineHeight: 1.2
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.preferredHeight: Math.max(feedUrlLayout.height, feedUrlCopyButton.width)
|
||||||
|
visible: page.showMoreInfo
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
RowLayout {
|
||||||
|
id: feedUrlLayout
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: feedUrlCopyButton.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
TextEdit {
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
readOnly: true
|
||||||
|
textFormat:TextEdit.RichText
|
||||||
|
text: i18n("Podcast URL:")
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
}
|
||||||
|
TextEdit {
|
||||||
|
id: feedUrl
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
|
textFormat:TextEdit.RichText
|
||||||
|
text: "<a href='%1'>%1</a>".arg(feed.url)
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Controls.Button {
|
||||||
|
id: feedUrlCopyButton
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
icon.name: "edit-copy"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
feedUrl.selectAll();
|
||||||
|
feedUrl.copy();
|
||||||
|
feedUrl.deselect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
visible: page.showMoreInfo
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
TextEdit {
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
readOnly: true
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
text: i18n("Weblink:")
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEdit {
|
||||||
|
readOnly: true
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
|
textFormat:TextEdit.RichText
|
||||||
|
text: "<a href='%1'>%1</a>".arg(feed.link)
|
||||||
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextEdit {
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: isSubscribed && page.showMoreInfo
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
|
textFormat:TextEdit.RichText
|
||||||
|
text: isSubscribed ? i18n("Subscribed since: %1", feed.subscribed.toLocaleString(Qt.locale(), Locale.ShortFormat)) : ""
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
}
|
||||||
|
TextEdit {
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: isSubscribed && page.showMoreInfo
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
|
textFormat:TextEdit.RichText
|
||||||
|
text: isSubscribed ? i18n("Last Updated: %1", feed.lastUpdated.toLocaleString(Qt.locale(), Locale.ShortFormat)) : ""
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
}
|
||||||
|
TextEdit {
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: isSubscribed && page.showMoreInfo
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
|
textFormat:TextEdit.RichText
|
||||||
|
text: i18np("1 Episode", "%1 Episodes", feed.entryCount) + ", " + i18np("1 Unplayed", "%1 Unplayed", feed.unreadEntryCount)
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
visible: entryList.count === 0 && isSubscribed
|
||||||
|
|
||||||
|
Kirigami.PlaceholderMessage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: Kirigami.Units.gridUnit * 20
|
||||||
|
|
||||||
|
text: feed.errorId === 0 ? i18n("No Episodes Available") : i18n("Error (%1): %2", feed.errorId, feed.errorString)
|
||||||
|
icon.name: feed.errorId === 0 ? "" : "data-error"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
TextEdit {
|
|
||||||
readOnly: true
|
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
|
||||||
textFormat:TextEdit.RichText
|
|
||||||
text: isSubscribed ? i18n("Subscribed since: %1", feed.subscribed.toLocaleString(Qt.locale(), Locale.ShortFormat)) : ""
|
|
||||||
visible: isSubscribed
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
}
|
|
||||||
TextEdit {
|
|
||||||
readOnly: true
|
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
|
||||||
textFormat:TextEdit.RichText
|
|
||||||
text: isSubscribed ? i18n("Last Updated: %1", feed.lastUpdated.toLocaleString(Qt.locale(), Locale.ShortFormat)) : ""
|
|
||||||
visible: isSubscribed
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
}
|
|
||||||
TextEdit {
|
|
||||||
readOnly: true
|
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
|
||||||
textFormat:TextEdit.RichText
|
|
||||||
text: i18np("1 Episode", "%1 Episodes", feed.entryCount) + ", " + i18np("1 Unplayed", "%1 Unplayed", feed.unreadEntryCount)
|
|
||||||
visible: isSubscribed
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,7 @@ Controls.ItemDelegate {
|
|||||||
lastFeed = feed.url
|
lastFeed = feed.url
|
||||||
if (pageStack.depth > 1)
|
if (pageStack.depth > 1)
|
||||||
pageStack.pop();
|
pageStack.pop();
|
||||||
pageStack.push("qrc:/EntryListPage.qml", {"feed": feed})
|
pageStack.push("qrc:/FeedDetailsPage.qml", {"feed": feed})
|
||||||
}
|
}
|
||||||
|
|
||||||
Controls.ToolTip {
|
Controls.ToolTip {
|
||||||
|
@ -11,7 +11,7 @@ import Qt.labs.platform 1.1
|
|||||||
import QtQuick.Layouts 1.14
|
import QtQuick.Layouts 1.14
|
||||||
import QtQml.Models 2.15
|
import QtQml.Models 2.15
|
||||||
|
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
import org.kde.kirigami 2.19 as Kirigami
|
||||||
|
|
||||||
import org.kde.kasts 1.0
|
import org.kde.kasts 1.0
|
||||||
|
|
||||||
@ -57,11 +57,13 @@ Kirigami.ScrollablePage {
|
|||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18n("Import Podcasts...")
|
text: i18n("Import Podcasts...")
|
||||||
iconName: "document-import"
|
iconName: "document-import"
|
||||||
|
displayHint: Kirigami.DisplayHint.AlwaysHide
|
||||||
onTriggered: importDialog.open()
|
onTriggered: importDialog.open()
|
||||||
},
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18n("Export Podcasts...")
|
text: i18n("Export Podcasts...")
|
||||||
iconName: "document-export"
|
iconName: "document-export"
|
||||||
|
displayHint: Kirigami.DisplayHint.AlwaysHide
|
||||||
onTriggered: exportDialog.open()
|
onTriggered: exportDialog.open()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -106,6 +108,7 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
GridView {
|
GridView {
|
||||||
id: feedList
|
id: feedList
|
||||||
|
currentIndex: -1
|
||||||
visible: count !== 0
|
visible: count !== 0
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
@ -14,11 +14,16 @@ import org.kde.kirigami 2.14 as Kirigami
|
|||||||
import org.kde.kasts 1.0
|
import org.kde.kasts 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
id: root
|
||||||
required property string image
|
required property string image
|
||||||
required property string title
|
required property string title
|
||||||
|
|
||||||
property string subtitle: ""
|
property string subtitle: ""
|
||||||
property var headerHeight: Kirigami.Units.gridUnit * 8
|
property var headerHeight: Kirigami.Units.gridUnit * 8
|
||||||
|
|
||||||
|
property bool clickable: false
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
implicitHeight: headerHeight
|
implicitHeight: headerHeight
|
||||||
implicitWidth: parent.width
|
implicitWidth: parent.width
|
||||||
|
|
||||||
@ -41,6 +46,15 @@ Item {
|
|||||||
color:"#87000000" //RGBA, but first value is actually the alpha channel
|
color:"#87000000" //RGBA, but first value is actually the alpha channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
if (root.clickable) {
|
||||||
|
root.clicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
property int size: Kirigami.Units.gridUnit * 6
|
property int size: Kirigami.Units.gridUnit * 6
|
||||||
property int margin: Kirigami.Units.gridUnit * 1
|
property int margin: Kirigami.Units.gridUnit * 1
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file alias="main.qml">qml/main.qml</file>
|
<file alias="main.qml">qml/main.qml</file>
|
||||||
<file alias="EntryListPage.qml">qml/EntryListPage.qml</file>
|
|
||||||
<file alias="FeedListPage.qml">qml/FeedListPage.qml</file>
|
<file alias="FeedListPage.qml">qml/FeedListPage.qml</file>
|
||||||
<file alias="EntryPage.qml">qml/EntryPage.qml</file>
|
<file alias="EntryPage.qml">qml/EntryPage.qml</file>
|
||||||
<file alias="AboutPage.qml">qml/Settings/AboutPage.qml</file>
|
<file alias="AboutPage.qml">qml/Settings/AboutPage.qml</file>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user