.ts'
13 |
--------------------------------------------------------------------------------
/.xdata/screenshots/screenshot-screenshot-storeman-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/screenshots/screenshot-screenshot-storeman-01.png
--------------------------------------------------------------------------------
/.xdata/screenshots/screenshot-screenshot-storeman-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/screenshots/screenshot-screenshot-storeman-02.png
--------------------------------------------------------------------------------
/.xdata/screenshots/screenshot-screenshot-storeman-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/screenshots/screenshot-screenshot-storeman-03.png
--------------------------------------------------------------------------------
/.xdata/screenshots/screenshot-screenshot-storeman-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/screenshots/screenshot-screenshot-storeman-04.png
--------------------------------------------------------------------------------
/.xdata/screenshots/screenshot-screenshot-storeman-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/screenshots/screenshot-screenshot-storeman-06.png
--------------------------------------------------------------------------------
/.xdata/screenshots/screenshot-screenshot-storeman-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/screenshots/screenshot-screenshot-storeman-07.png
--------------------------------------------------------------------------------
/.xdata/screenshots/screenshot-screenshot-storeman-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/screenshots/screenshot-screenshot-storeman-08.png
--------------------------------------------------------------------------------
/.xdata/screenshots/screenshot-screenshot-storeman-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/screenshots/screenshot-screenshot-storeman-09.png
--------------------------------------------------------------------------------
/.xdata/social-media-icons/harbour-storeman_1280x640.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/social-media-icons/harbour-storeman_1280x640.png
--------------------------------------------------------------------------------
/.xdata/social-media-icons/harbour-storeman_1500x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/social-media-icons/harbour-storeman_1500x500.png
--------------------------------------------------------------------------------
/.xdata/social-media-icons/harbour-storeman_792x792.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/social-media-icons/harbour-storeman_792x792.png
--------------------------------------------------------------------------------
/.xdata/social-media-icons/harbour-storeman_960x640.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/.xdata/social-media-icons/harbour-storeman_960x640.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022-2024 olf (Olf0)
4 | 2017-2022 Petr Tsymbarovich (mentaljam / osetr)
5 | 2019-2022 Björn Bidar (Thaodan)
6 | 2024 Peter G. (nephros)
7 | 2024 citronalco
8 | 2020 Dmitry Gerasimov (dseight)
9 | 2019 Matti Viljanen (direc85)
10 | 2019 Miklós Márton (martonmiklos)
11 | 2018 elros34
12 | 2017 Christoph (inta)
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the "Software"), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice (including the next
22 | paragraph) shall be included in all copies or substantial portions of the
23 | Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | SOFTWARE.
32 |
33 | Canonical source for this license: https://spdx.org/licenses/MIT.html
34 |
--------------------------------------------------------------------------------
/data/harbour-storeman:
--------------------------------------------------------------------------------
1 | # Allow harbour-storeman to manage packages
2 | /usr/bin/harbour-storeman,r
3 |
--------------------------------------------------------------------------------
/data/harbour.storeman.service:
--------------------------------------------------------------------------------
1 | [D-BUS Service]
2 | Name=harbour.storeman.service
3 | Exec=/usr/bin/invoker --type=silica-qt5 -s /usr/bin/harbour-storeman
4 |
--------------------------------------------------------------------------------
/harbour-storeman.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Type=Application
3 | X-Nemo-Application-Type=silica-qt5
4 | Icon=harbour-storeman
5 | Exec=harbour-storeman
6 | Name=Storeman
7 | Categories=System;Utility;Network;Settings;PackageManager;
8 |
9 | [X-HarbourBackup]
10 | BackupPathList=.config/harbour-storeman/:.local/share/harbour-storeman/
11 |
--------------------------------------------------------------------------------
/icons/108x108/harbour-storeman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/icons/108x108/harbour-storeman.png
--------------------------------------------------------------------------------
/icons/128x128/harbour-storeman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/icons/128x128/harbour-storeman.png
--------------------------------------------------------------------------------
/icons/172x172/harbour-storeman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/icons/172x172/harbour-storeman.png
--------------------------------------------------------------------------------
/icons/256x256/harbour-storeman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/icons/256x256/harbour-storeman.png
--------------------------------------------------------------------------------
/icons/480x480/harbour-storeman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/icons/480x480/harbour-storeman.png
--------------------------------------------------------------------------------
/icons/560x560/harbour-storeman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/icons/560x560/harbour-storeman.png
--------------------------------------------------------------------------------
/icons/86x86/harbour-storeman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/storeman-developers/harbour-storeman/732b7370fba26a0206e0501a72ad2e4b4c992c56/icons/86x86/harbour-storeman.png
--------------------------------------------------------------------------------
/qml/StoremanStyles.qml:
--------------------------------------------------------------------------------
1 | pragma Singleton
2 | import QtQuick 2.0
3 | import Sailfish.Silica 1.0
4 |
5 |
6 | QtObject {
7 | readonly property string commentStyle: "
8 | "
23 | }
24 |
--------------------------------------------------------------------------------
/qml/components/AppInfoLabel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | Label {
5 | property string label
6 | property string value
7 |
8 | width: parent.width
9 | color: Theme.highlightColor
10 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere
11 | font.pixelSize: Theme.fontSizeExtraSmall
12 | textFormat: Text.StyledText
13 | text: "%1 %2"
14 | .arg(Theme.secondaryHighlightColor)
15 | .arg(label)
16 | .arg(value.replace(/\n/g, " "))
17 | }
18 |
--------------------------------------------------------------------------------
/qml/components/AppPageMenu.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import org.nemomobile.lipstick 0.1
4 | import harbour.orn 1.0
5 |
6 | PullDownMenu {
7 | readonly property bool _enableMenu: networkManager.connected &&
8 | !itemInProgress(app.repoAlias) &&
9 | !itemInProgress(app.packageName)
10 |
11 | id: pullMenu
12 | visible: OrnPm.initialised
13 |
14 | MenuItem {
15 | enabled: !app.running
16 | //% "Reload"
17 | text: qsTrId("orn-reload")
18 | onClicked: {
19 | flickable.visible = false
20 | app.ornRequest()
21 | }
22 | }
23 |
24 | MenuItem {
25 | id: repoMenuItem
26 | visible: text
27 | enabled: _enableMenu
28 | text: {
29 | if (!app.packageName || !app.repoAlias) {
30 | return ""
31 | }
32 | switch (app.repoStatus) {
33 | case OrnPm.RepoNotInstalled:
34 | //% "Add repository"
35 | return qsTrId("orn-repo-add")
36 | case OrnPm.RepoDisabled:
37 | //% "Enable repository"
38 | return qsTrId("orn-repo-enable")
39 | default:
40 | return qsTrId("orn-refresh-cache")
41 | }
42 | }
43 |
44 | onClicked: {
45 | switch (app.repoStatus) {
46 | case OrnPm.RepoNotInstalled:
47 | //% "Adding"
48 | Remorse.popupAction(page, qsTrId("orn-adding-repo"), function() {
49 | OrnPm.addRepo(app.userName)
50 | })
51 | break
52 | case OrnPm.RepoDisabled:
53 | OrnPm.modifyRepo(app.repoAlias, OrnPm.EnableRepo)
54 | break
55 | default:
56 | OrnPm.refreshRepo(app.repoAlias, true)
57 | break
58 | }
59 | }
60 | }
61 |
62 | MenuItem {
63 | id: installMenuItem
64 | visible: text
65 | enabled: _enableMenu && app.packageName
66 | text: {
67 | switch (_packageStatus) {
68 | case OrnPm.PackageAvailable:
69 | //% "Install"
70 | return qsTrId("orn-install")
71 | case OrnPm.PackageInstalled:
72 | case OrnPm.PackageUpdateAvailable:
73 | //% "Remove"
74 | return qsTrId("orn-remove")
75 | default:
76 | // TODO: This also should be shown for apps with no solved packages
77 | // (for example when there is no i486 packages)
78 | //% "No packages available"
79 | return app.packageName ? "" : qsTrId("orn-no-packages")
80 |
81 | }
82 | }
83 |
84 | onClicked: {
85 | switch (_packageStatus) {
86 | case OrnPm.PackageAvailable:
87 | OrnPm.installPackage(app.availableId)
88 | break
89 | case OrnPm.PackageInstalled:
90 | case OrnPm.PackageUpdateAvailable:
91 | Remorse.popupAction(page, qsTrId("orn-removing"), function() {
92 | OrnPm.removePackage(app.installedId)
93 | })
94 | break
95 | default:
96 | break
97 | }
98 | }
99 | }
100 |
101 | MenuItem {
102 | id: updateMenuItem
103 | visible: _packageStatus == OrnPm.PackageUpdateAvailable
104 | enabled: _enableMenu
105 | //% "Update"
106 | text: qsTrId("orn-update")
107 | onClicked: OrnPm.updatePackage(app.packageName)
108 | }
109 |
110 | MenuItem {
111 | id: launchMenuItem
112 | visible: app.desktopFile
113 | //% "Launch"
114 | text: qsTrId("orn-launch")
115 | onClicked: launcher.launchApplication()
116 |
117 | LauncherItem {
118 | id: launcher
119 | filePath: app.desktopFile
120 | }
121 | }
122 |
123 | MenuStatusLabel { }
124 | }
125 |
--------------------------------------------------------------------------------
/qml/components/BackupLabel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 |
5 | Label {
6 | color: Theme.highlightColor
7 | wrapMode: Text.WordWrap
8 | font.pixelSize: Theme.fontSizeSmall
9 | //% "Backup to a file "
10 | //% "Backup allows you to save your current OpenRepos repositories, installed applications and bookmarks and "
11 | //% "restore them later (for example after factory reset). A backup is a local file that is saved to the "
12 | //% "~/Documents/Storeman directory.
"
13 | //% "Attention! You should copy your backups manually to some safe place before performing a factory reset. "
14 | //% "It could be your SD card, external device, cloud storage or something else.
"
15 | text: qsTrId("orn-backup-hint")
16 | }
17 |
--------------------------------------------------------------------------------
/qml/components/BackupOptions.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 |
5 |
6 | Column {
7 | readonly property string path: {
8 | var p = StandardPaths.documents + "/Storeman/" + textField.text
9 | if (p.lastIndexOf('.ini') === -1) {
10 | p += '.ini'
11 | }
12 | return p.trim()
13 | }
14 | readonly property bool _fileExists: path && Storeman.fileExists(path)
15 | readonly property bool acceptable: !textField.errorHighlight
16 |
17 | width: parent.width
18 |
19 | TextField {
20 | id: textField
21 | width: parent.width
22 | //% "A file name for backup"
23 | placeholderText: qsTrId("orn-backup-filenameph")
24 | inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
25 | validator: RegExpValidator {
26 | regExp: /((?!\/).)+/
27 | }
28 | errorHighlight: !acceptableInput || _fileExists
29 | text: backupOptions.filename
30 | //% "File already exists"
31 | label: _fileExists ? qsTrId("orn-file-exists") : placeholderText
32 |
33 | onTextChanged: backupOptions.filename = text
34 | }
35 |
36 | SectionHeader {
37 | //% "What to backup"
38 | text: qsTrId("orn-backup-items")
39 | }
40 |
41 | TextSwitch {
42 | checked: backupOptions.repos
43 | text: qsTrId("orn-repositories")
44 |
45 | onCheckedChanged: backupOptions.repos = checked
46 | }
47 |
48 | TextSwitch {
49 | checked: backupOptions.installed
50 | //% "Installed applications"
51 | text: qsTrId("orn-backup-apps")
52 |
53 | onCheckedChanged: backupOptions.installed = checked
54 | }
55 |
56 | TextSwitch {
57 | checked: backupOptions.bookmarks
58 | text: qsTrId("orn-bookmarks")
59 |
60 | onCheckedChanged: backupOptions.bookmarks = checked
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/qml/components/BookmarkButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 |
5 | IconButton {
6 | property int appId
7 | property var bookmarked: undefined
8 | property bool _updateState
9 |
10 | icon.source: (bookmarked ? "image://theme/icon-m-favorite-selected?" :
11 | "image://theme/icon-m-favorite?") +
12 | (pressed ? Theme.highlightColor : Theme.primaryColor)
13 |
14 | onClicked: {
15 | var f = bookmarked ? OrnClient.removeBookmark : OrnClient.addBookmark
16 | f(appId)
17 | if (_updateState) {
18 | bookmarked = !bookmarked
19 | }
20 | }
21 |
22 | Component.onCompleted: {
23 | _updateState = bookmarked === undefined
24 | if (_updateState) {
25 | bookmarked = OrnClient.hasBookmark(appId)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/qml/components/CategoriesFilterDelegate.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | BackgroundItem {
5 | property bool categoryVisible
6 | property alias text: label.text
7 | property alias textAlignment: label.horizontalAlignment
8 | property int depth: 0
9 | readonly property color _color: categoryVisible ? highlighted ? Theme.highlightColor : Theme.primaryColor : Theme.secondaryColor
10 |
11 | height: Theme.itemSizeSmall
12 | opacity: categoryVisible ? 1.0 : Theme.opacityLow
13 |
14 | Label {
15 | id: label
16 | anchors {
17 | left: parent.left
18 | right: image.left
19 | verticalCenter: parent.verticalCenter
20 | leftMargin: Theme.horizontalPageMargin + depth * Theme.paddingLarge
21 | rightMargin: Theme.paddingMedium
22 | }
23 | horizontalAlignment: Text.AlignRight
24 | truncationMode: TruncationMode.Fade
25 | color: _color
26 | }
27 |
28 | Image {
29 | id: image
30 | anchors {
31 | right: parent.right
32 | verticalCenter: parent.verticalCenter
33 | rightMargin: Theme.horizontalPageMargin
34 | }
35 | source: (categoryVisible ? "image://theme/icon-m-accept?" : "image://theme/icon-m-cancel?") + _color
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/qml/components/CommentLabel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | BackgroundItem {
5 | width: Screen.width
6 | height: Theme.itemSizeLarge
7 | onClicked: if (networkManager.connected)
8 | pageStack.push(Qt.resolvedUrl("../pages/AuthorisationDialog.qml"))
9 |
10 | Label {
11 | anchors {
12 | left: parent.left
13 | right: parent.right
14 | margins: Theme.horizontalPageMargin
15 | verticalCenter: parent.verticalCenter
16 | }
17 | color: parent.pressed ? Theme.highlightColor : Theme.primaryColor
18 | wrapMode: Text.WordWrap
19 | //% "Login to comment"
20 | text: networkManager.connected ? qsTrId("orn-login2comment") :
21 | qsTrId("orn-network-idle")
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/qml/components/DisappearAnimation.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 |
3 |
4 | ParallelAnimation {
5 | property bool show: true
6 | property var target: null
7 |
8 | id: animation
9 |
10 | onShowChanged: {
11 | if (show) {
12 | oa.from = 0.0
13 | oa.to = 1.0
14 | na.from = 0.0
15 | na.to = target.implicitHeight
16 | } else {
17 | oa.from = 1.0
18 | oa.to = 0.0
19 | na.from = target.implicitHeight
20 | na.to = 0.0
21 | }
22 | start()
23 | }
24 |
25 | OpacityAnimator {
26 | id: oa
27 | target: animation.target
28 | duration: 200
29 | easing.type: Easing.InOutQuad
30 | }
31 |
32 | NumberAnimation {
33 | id: na
34 | target: animation.target
35 | property: "height"
36 | duration: 200
37 | easing.type: Easing.InOutQuad
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/qml/components/FancyPageHeader.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | Item {
5 | property alias title: header.title
6 | property alias description: header.description
7 | property alias iconSource: image.source
8 |
9 | width: parent.width
10 | height: header.height
11 |
12 | PageHeader {
13 | id: header
14 | width: parent.width -
15 | (image.visible ? image.width + Theme.paddingMedium : 0)
16 | }
17 |
18 | Image {
19 | id: image
20 | visible: source.toString()
21 | anchors {
22 | verticalCenter: header.verticalCenter
23 | right: parent.right
24 | rightMargin: Theme.paddingMedium
25 | }
26 | width: Theme.iconSizeLauncher
27 | height: Theme.iconSizeLauncher
28 | fillMode: Image.PreserveAspectFit
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/qml/components/HtmlTagButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | // Silica button height is hardcoded
5 | MouseArea {
6 | property string tag
7 | property string attrs: ""
8 | property alias text: buttonText.text
9 | property bool _showPress: (pressed && containsMouse) || pressTimer.running
10 |
11 | height: Theme.itemSizeExtraSmall * 0.75
12 | width: Math.max(height, buttonText.width + Theme.paddingLarge)
13 |
14 | onPressedChanged: if (pressed) pressTimer.start()
15 | onCanceled: pressTimer.stop()
16 |
17 | onClicked: {
18 | var editor = body._editor
19 | var selected = editor.selectedText
20 | if (selected) {
21 | editor.remove(editor.selectionStart, editor.selectionEnd)
22 | }
23 | editor.insert(editor.cursorPosition, "<%0%1>%2%0>".arg(tag).arg(attrs).arg(selected))
24 | if (!selected) {
25 | // 3 is for ">" length
26 | editor.cursorPosition = editor.cursorPosition - tag.length - 3
27 | }
28 | }
29 |
30 | Rectangle {
31 | anchors.fill: parent
32 | radius: Theme.paddingSmall
33 | color: _showPress ? Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity)
34 | : Theme.rgba(Theme.primaryColor, 0.2)
35 |
36 | Label {
37 | id: buttonText
38 | anchors.centerIn: parent
39 | horizontalAlignment: Qt.AlignHCenter
40 | color: _showPress ? Theme.highlightColor : Theme.primaryColor
41 | textFormat: Text.RichText
42 | }
43 | }
44 |
45 | Timer {
46 | id: pressTimer
47 | interval: Theme.minimumPressHighlightTime
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/qml/components/IconLabel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | Row {
5 | property bool running: false
6 | property string icon
7 | property alias text: label.text
8 |
9 | spacing: Theme.paddingSmall
10 |
11 | Loader {
12 | anchors.verticalCenter: parent.verticalCenter
13 | sourceComponent: running ? busyComponent : iconComponent
14 | }
15 |
16 | Component {
17 | id: busyComponent
18 | BusyIndicator {
19 | size: BusyIndicatorSize.ExtraSmall
20 | running: true
21 | }
22 | }
23 |
24 | Component {
25 | id: iconComponent
26 | Image {
27 | source: icon ? icon + "?" + Theme.highlightColor : ""
28 | }
29 | }
30 |
31 | Label {
32 | id: label
33 | anchors.verticalCenter: parent.verticalCenter
34 | font.pixelSize: Theme.fontSizeExtraSmall
35 | color: Theme.highlightColor
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/qml/components/IntervalView.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | SilicaListView {
5 | id: view
6 | snapMode: ListView.SnapToItem
7 | clip: true
8 | quickScrollEnabled: false
9 |
10 | delegate: BackgroundItem {
11 | width: view.width
12 | height: currentIndex === index
13 | ? Theme.itemSizeLarge
14 | : Theme.itemSizeSmall
15 |
16 | Behavior on height { NumberAnimation {} }
17 |
18 | onClicked: {
19 | currentIndex = index
20 | animation.moveTo(currentIndex)
21 | }
22 |
23 | Label {
24 | text: index
25 | anchors.centerIn: parent
26 | color: highlighted
27 | ? Theme.highlightColor
28 | : Theme.primaryColor
29 | font.pixelSize: currentIndex === index
30 | ? Theme.fontSizeHuge
31 | : Theme.fontSizeLarge
32 |
33 | Behavior on font.pixelSize { NumberAnimation {} }
34 | }
35 | }
36 |
37 | Component.onCompleted: positionViewAtIndex(currentIndex, ListView.Center)
38 |
39 | ListViewPositionAnimation {
40 | id: animation
41 | target: view
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/qml/components/ListMenuItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 |
5 | ListItem {
6 | property alias text: label.text
7 | property string iconSource
8 | readonly property string _color:
9 | pressed ? Theme.highlightColor :
10 | enabled ? Theme.primaryColor : Theme.secondaryHighlightColor
11 |
12 | id: item
13 | width: parent.width
14 | contentHeight: Theme.itemSizeLarge
15 |
16 | Row {
17 | anchors {
18 | left: parent.left
19 | right: parent.right
20 | leftMargin: Theme.horizontalPageMargin
21 | rightMargin: Theme.horizontalPageMargin
22 | verticalCenter: parent.verticalCenter
23 | }
24 | spacing: Theme.paddingMedium
25 |
26 | Image {
27 | id: icon
28 | anchors.verticalCenter: parent.verticalCenter
29 | width: Theme.iconSizeMedium
30 | height: Theme.iconSizeMedium
31 | fillMode: Image.PreserveAspectFit
32 | source: iconSource ? (iconSource + "?" + _color) : ""
33 | }
34 |
35 | Label {
36 | id: label
37 | anchors.verticalCenter: parent.verticalCenter
38 | color: _color
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/qml/components/ListViewPositionAnimation.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 |
3 | NumberAnimation {
4 | property var mode: ListView.Center
5 |
6 | function moveTo(index) {
7 | stop()
8 | from = target.contentY
9 | target.positionViewAtIndex(index, mode)
10 | // FIXME: Sometimes it's not defined
11 | to = target.contentY
12 | start()
13 | }
14 |
15 | property: "contentY"
16 | }
17 |
--------------------------------------------------------------------------------
/qml/components/MainPageAppGridDelegate.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | BackgroundItem {
5 | id: delegate
6 | width: cellWidth
7 | height: Theme.itemSizeSmall
8 | visible: model.index < gridColumns * gridRows
9 |
10 | onClicked: {
11 | if (!model.isValid) {
12 | return
13 | }
14 | pageStack.push(Qt.resolvedUrl("../pages/AppPage.qml"), {
15 | appId: model.appId
16 | })
17 | }
18 |
19 | Image {
20 | id: icon
21 | anchors {
22 | left: parent.left
23 | leftMargin: Theme.horizontalPageMargin
24 | verticalCenter: parent.verticalCenter
25 | }
26 | width: Theme.iconSizeMedium
27 | height: Theme.iconSizeMedium
28 | source: model.iconSource
29 | }
30 |
31 | Item {
32 | id: labelItem
33 | anchors {
34 | left: icon.right
35 | leftMargin: Theme.paddingMedium
36 | right: parent.right
37 | rightMargin: Theme.horizontalPageMargin
38 | verticalCenter: parent.verticalCenter
39 | }
40 | height: authorLabel.y + authorLabel.height
41 |
42 | Label {
43 | id: titleLabel
44 | width: parent.width
45 | text: model.title
46 | color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor
47 | font.pixelSize: Theme.fontSizeTiny
48 | truncationMode: TruncationMode.Fade
49 | }
50 |
51 | Label {
52 | id: authorLabel
53 | anchors.top: titleLabel.bottom
54 | width: parent.width
55 | text: model.userName
56 | color: Theme.secondaryHighlightColor
57 | font.pixelSize: Theme.fontSizeTiny
58 | truncationMode: TruncationMode.Fade
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/qml/components/MainPageButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | MoreButton {
5 | height: Theme.itemSizeMedium
6 |
7 | Rectangle {
8 | anchors.fill: parent
9 | gradient: Gradient {
10 | GradientStop { position: 0.0; color: Theme.rgba(Theme.highlightBackgroundColor, 0.15) }
11 | GradientStop { position: 1.0; color: "transparent" }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/qml/components/MenuSearchItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | MenuItem {
5 | visible: networkManager.connected
6 | text: qsTrId("orn-search")
7 | onClicked: pageStack.push(Qt.resolvedUrl("../pages/SearchPage.qml"))
8 | }
9 |
--------------------------------------------------------------------------------
/qml/components/MenuStatusLabel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 |
5 | MenuLabel {
6 | property PullDownMenu __menu: parent.parent
7 |
8 | visible: text
9 | text: {
10 | if (!OrnPm.initialised) {
11 | __menu.busy = true
12 | //% "Initialising"
13 | return qsTrId("orn-pmstate-initialising")
14 | }
15 |
16 | var len = _operations.length
17 |
18 | if (len === 0) {
19 | __menu.busy = false
20 | return ""
21 | }
22 |
23 | if (len > 1) {
24 | __menu.busy = true
25 | //: There are always more than 1 operations
26 | //% "%n operations are in progress"
27 | return qsTrId("orn-pmstate-multiple", _operations.length)
28 | }
29 |
30 | __menu.busy = true
31 | var op = _operations[0]
32 | switch (op.operation) {
33 | case OrnPm.AddingRepo:
34 | //% "Adding repo %0"
35 | return qsTrId("orn-pmstate-addingrepo").arg(op.item)
36 | case OrnPm.RemovingRepo:
37 | //% "Removing repo %0"
38 | return qsTrId("orn-pmstate-removingrepo").arg(op.item)
39 | case OrnPm.EnablingRepo:
40 | //% "Enabling repo %0"
41 | return qsTrId("orn-pmstate-enablingrepo").arg(op.item)
42 | case OrnPm.DisablingRepo:
43 | //% "Disabling repo %0"
44 | return qsTrId("orn-pmstate-disablingrepo").arg(op.item)
45 | case OrnPm.RefreshingRepo:
46 | //% "Refreshing %0"
47 | return qsTrId("orn-pmstate-refreshingrepo").arg(op.item)
48 | case OrnPm.InstallingPackage:
49 | //% "Installing package %0"
50 | return qsTrId("orn-pmstate-installingpackage").arg(op.item)
51 | case OrnPm.RemovingPackage:
52 | //% "Removing package %0"
53 | return qsTrId("orn-pmstate-removingpackage").arg(op.item)
54 | case OrnPm.UpdatingPackage:
55 | //% "Updating package %0"
56 | return qsTrId("orn-pmstate-updatingpackage").arg(op.item)
57 | case OrnPm.RefreshingCache:
58 | //% "Refreshing of cache"
59 | return qsTrId("orn-pmstate-refreshingcache")
60 | default:
61 | return ""
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/qml/components/MoreButton.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | BackgroundItem {
5 | property alias text: label.text
6 | property alias textAlignment: label.horizontalAlignment
7 | property int depth: 0
8 | readonly property color _color: enabled ? highlighted ? Theme.highlightColor : Theme.primaryColor : Theme.secondaryColor
9 |
10 | height: Theme.itemSizeSmall
11 |
12 | Label {
13 | id: label
14 | anchors {
15 | left: parent.left
16 | right: image.left
17 | verticalCenter: parent.verticalCenter
18 | leftMargin: Theme.horizontalPageMargin + depth * Theme.paddingLarge
19 | rightMargin: Theme.paddingMedium
20 | }
21 | horizontalAlignment: Text.AlignRight
22 | truncationMode: TruncationMode.Fade
23 | color: _color
24 | }
25 |
26 | Image {
27 | id: image
28 | anchors {
29 | right: parent.right
30 | verticalCenter: parent.verticalCenter
31 | rightMargin: Theme.horizontalPageMargin
32 | }
33 | source: "image://theme/icon-m-right?" + _color
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/qml/components/ParticipantsDelegate.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | Column {
5 | property alias title: label.text
6 | property alias model: repeater.model
7 |
8 | width: parent.width
9 | visible: model && model.count
10 |
11 | Label {
12 | id: label
13 | width: parent.width
14 | wrapMode: Text.WordWrap
15 | horizontalAlignment: Qt.AlignHCenter
16 | color: Theme.secondaryHighlightColor
17 | font.pixelSize: Theme.fontSizeSmall
18 | }
19 |
20 | Repeater {
21 | id: repeater
22 |
23 | delegate: Label {
24 | width: parent.width
25 | wrapMode: Text.WordWrap
26 | horizontalAlignment: Qt.AlignHCenter
27 | color: Theme.secondaryColor
28 | font.pixelSize: Theme.fontSizeSmall
29 | text: name
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/qml/components/RatingBox.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | Loader {
5 | property int ratingCount
6 | property real rating
7 | readonly property int _rating: rating / 20.0 + 0.5
8 |
9 | sourceComponent: ratingCount > 0 ? starBox : notRated
10 |
11 | Component {
12 | id: starBox
13 |
14 | Row {
15 | Repeater {
16 | id: ratingBox
17 | model: 5
18 |
19 | Image {
20 | width: Theme.iconSizeExtraSmall
21 | height: Theme.iconSizeExtraSmall
22 | opacity: index < _rating ? 1.0 : 0.5
23 | source: "image://theme/icon-s-favorite?" +
24 | (index < _rating ?
25 | "gold" : Theme.secondaryColor)
26 | }
27 | }
28 | }
29 | }
30 |
31 | Component {
32 | id: notRated
33 |
34 | Label {
35 | height: Theme.iconSizeExtraSmall
36 | font.pixelSize: Theme.fontSizeTiny
37 | color: Theme.secondaryColor
38 | //% "Not rated yet"
39 | text: qsTrId("orn-notrated")
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/qml/components/RefreshMenuItem.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | MenuItem {
5 | property var model
6 |
7 | visible: model
8 | enabled: networkManager.connected
9 | //% "Refresh"
10 | text: qsTrId("orn-refresh")
11 | onClicked: model.reset()
12 | }
13 |
--------------------------------------------------------------------------------
/qml/components/ScreenshotsBox.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | Row {
5 | id: screenshotBox
6 | width: parent.width
7 |
8 | Repeater {
9 | model: Math.min(app.screenshots.length, 3)
10 |
11 | Image {
12 | width: screenshotBox.width / 3
13 | height: width
14 | fillMode: Image.PreserveAspectCrop
15 | source: app.screenshots[index].thumb
16 |
17 | BusyIndicator {
18 | anchors.centerIn: parent
19 | running: parent.status === Image.Loading
20 | }
21 |
22 | MouseArea {
23 | anchors.fill: parent
24 | onClicked: pageStack.push(
25 | Qt.resolvedUrl("../pages/ScreenshotPage.qml"), {
26 | model: app.screenshots,
27 | currentIndex: model.index
28 | }, PageStackAction.Immediate)
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/qml/components/StoremanHintLabel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 |
5 | MouseArea {
6 | property bool _doClose: false
7 | property var hint
8 | property string text
9 | property alias invert: label.invert
10 |
11 | signal finished()
12 |
13 | anchors.fill: parent
14 |
15 | onClicked: {
16 | if (_doClose) {
17 | timer.stop()
18 | hint.stop()
19 | } else {
20 | _doClose = true
21 | timer.start()
22 | }
23 | }
24 |
25 | Timer {
26 | id: timer
27 | interval: 2000
28 | onTriggered: _doClose = false
29 | }
30 |
31 | InteractionHintLabel {
32 | id: label
33 | anchors {
34 | top: invert ? parent.top : undefined
35 | bottom: invert ? undefined : parent.bottom
36 | }
37 | opacity: hint && hint.running ? 1.0 : 0.0
38 | //% "Tap again to close the hint"
39 | text: _doClose ? qsTrId("orn-hint-close") : parent.text
40 |
41 | Behavior on opacity {
42 | FadeAnimation {
43 | duration: 1000
44 |
45 | onRunningChanged: {
46 | if (!running && label.opacity === 0.0) {
47 | _doClose = false
48 | finished()
49 | }
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/qml/components/StoremanTapHint.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.2
2 | import Sailfish.Silica 1.0
3 |
4 | Image {
5 | property alias running: blinkAnimation.running
6 |
7 | function start() {
8 | blinkAnimation.start()
9 | }
10 |
11 | function stop() {
12 | blinkAnimation.stop()
13 | }
14 |
15 | id: root
16 | anchors.centerIn: parent
17 | source: "image://theme/graphic-gesture-hint?" + Theme.primaryColor
18 | opacity: 0.0
19 |
20 | onRunningChanged: if (!running) opacity = 0.0
21 |
22 | SequentialAnimation {
23 | id: blinkAnimation
24 | loops: Animation.Infinite
25 |
26 | OpacityAnimator {
27 | target: root
28 | from: 0.0
29 | to: 1.0
30 | duration: 200
31 | }
32 |
33 | PauseAnimation {
34 | duration: 200
35 | }
36 |
37 | OpacityAnimator {
38 | target: root
39 | from: 1.0
40 | to: 0.0
41 | duration: 800
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/qml/components/StoremanTouchInteractionHint.qml:
--------------------------------------------------------------------------------
1 | import Sailfish.Silica 1.0
2 |
3 | TouchInteractionHint {
4 | direction: TouchInteraction.Left
5 | loops: Infinity
6 | }
7 |
--------------------------------------------------------------------------------
/qml/components/TagDelegate.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 |
5 | ListItem {
6 | id: delegate
7 | contentHeight: label.height + Theme.paddingMedium * 2
8 |
9 | onClicked: pageStack.push(Qt.resolvedUrl("../pages/TagAppsPage.qml"),
10 | {
11 | tagId: model.tagId,
12 | tagName: model.name,
13 | previousAppId: page.previousAppId
14 | })
15 |
16 | Label {
17 | id: label
18 | anchors {
19 | verticalCenter: parent.verticalCenter
20 | left: parent.left
21 | right: parent.right
22 | leftMargin: Theme.horizontalPageMargin
23 | rightMargin: Theme.horizontalPageMargin
24 | }
25 | wrapMode: Text.WordWrap
26 | text: model.name
27 | color: delegate.highlighted ? Theme.highlightColor : Theme.primaryColor
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/qml/cover/CoverPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 |
5 | CoverBackground {
6 |
7 | function _activatePage(name, callback) {
8 | var namelength = name.length
9 | const pageOnStack = pageStack.find(function(p) {
10 | return p.toString().substr(0, namelength) === name
11 | })
12 | if (pageOnStack) {
13 | if (callback) {
14 | callback(pageOnStack)
15 | }
16 | pageStack.pop(pageOnStack, PageStackAction.Immediate)
17 | } else {
18 | pageStack.push(Qt.resolvedUrl("../pages/%1.qml".arg(name)), {}, PageStackAction.Immediate)
19 | }
20 | __silica_applicationwindow_instance.activate()
21 | }
22 |
23 | Image {
24 | anchors.fill: parent
25 | source: Qt.resolvedUrl("./background.svg")
26 | fillMode: Image.PreserveAspectFit
27 | opacity: 0.15
28 | }
29 |
30 | Label {
31 | anchors {
32 | horizontalCenter: parent.horizontalCenter
33 | top: parent.top
34 | topMargin: Theme.paddingLarge
35 | }
36 | color: networkManager.connected
37 | ? Theme.highlightColor
38 | : Theme.highlightDimmerColor
39 | horizontalAlignment: Text.AlignHCenter
40 | font.pixelSize: Theme.fontSizeLarge
41 | text: applicationDisplayName
42 | }
43 |
44 | Label {
45 | visible: OrnPm.updatesAvailable
46 | anchors.centerIn: parent
47 | width: parent.width - 2 * (Screen.sizeCategory > Screen.Medium
48 | ? Theme.paddingMedium
49 | : Theme.paddingLarge)
50 | color: Theme.primaryColor
51 | horizontalAlignment: Text.AlignHCenter
52 | wrapMode: Text.Wrap
53 | //% "Updates available"
54 | text: qsTrId("orn-cover-updates-available")
55 | }
56 |
57 | CoverActionList {
58 | id: coverAction
59 | enabled: networkManager.connected
60 |
61 | CoverAction {
62 | iconSource: "image://theme/icon-cover-search"
63 | onTriggered: _activatePage("SearchPage", function(p) {
64 | p.reset()
65 | })
66 | }
67 |
68 | CoverAction {
69 | iconSource: "image://theme/icon-cover-" + (OrnPm.updatesAvailable ? "next" : "sync")
70 | onTriggered: {
71 | if (OrnPm.updatesAvailable) {
72 | _activatePage("InstalledAppsPage")
73 | } else if (!OrnPm.refreshingCache) {
74 | OrnPm.refreshRepos()
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/qml/cover/background.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/qml/models/DevelopersModel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 |
3 | ListModel {
4 |
5 | ListElement {
6 | //% "Developers"
7 | role: qsTrId("orn-developers")
8 | participants: [
9 | ListElement {
10 | name: "Petr Tsymbarovich (mentaljam / osetr)"
11 | link: "https://github.com/mentaljam"
12 | },
13 | ListElement {
14 | name: "olf (Olf0)"
15 | link: "https://github.com/Olf0"
16 | },
17 | ListElement {
18 | name: "Peter G. (nephros)"
19 | link: "https://github.com/nephros"
20 | },
21 | ListElement {
22 | name: "Matti Viljanen (direc85)"
23 | link: "https://github.com/direc85"
24 | },
25 | ListElement {
26 | name: "Björn Bidar (Thaodan)"
27 | link: "https://github.com/Thaodan"
28 | },
29 | ListElement {
30 | name: "citronalco"
31 | link: "https://github.com/citronalco"
32 | },
33 | ListElement {
34 | name: "Dmitry Gerasimov (dseight)"
35 | link: "https://github.com/dseight"
36 | },
37 | ListElement {
38 | name: "elros34"
39 | link: "https://github.com/elros34"
40 | },
41 | ListElement {
42 | name: "Christoph (inta)"
43 | link: "https://github.com/inta"
44 | },
45 | ListElement {
46 | name: "Miklós Márton (martonmiklos)"
47 | link: "https://github.com/martonmiklos"
48 | }
49 | ]
50 | }
51 |
52 | ListElement {
53 | role: "OpenRepos"
54 | participants: [
55 | ListElement {
56 | name: "Basil Semuonov (custodian / thecust)"
57 | link: "https://github.com/custodian"
58 | }
59 | ]
60 | }
61 |
62 | ListElement {
63 | //% "Application icon"
64 | role: qsTrId("orn-appicon")
65 | participants: [
66 | ListElement {
67 | name: "Laurent Chambon (Laurent_C)"
68 | link: "mailto:l.chambon@gmail.com"
69 | }
70 | ]
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/qml/models/DummyCommentsModel.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 |
3 | ListModel {
4 |
5 | ListElement {
6 | userId: 1
7 | created: qsTrId("orn-just-now")
8 | commentId: 2
9 | //: https://simple.wikipedia.org/wiki/42_(answer)
10 | //% "Deep Thought"
11 | userName: qsTrId("orn-dcm-user2")
12 | text: "42"
13 | parentId: 1
14 | parentUserName: qsTrId("orn-dcm-user1")
15 | }
16 |
17 | ListElement {
18 | userId: 0
19 | //% "7.5 million years ago"
20 | created: qsTrId("orn-hint-commentdelegate-created")
21 | commentId: 1
22 | //: https://simple.wikipedia.org/wiki/42_(answer)
23 | //% "A little white mouse"
24 | userName: qsTrId("orn-dcm-user1")
25 | //: https://simple.wikipedia.org/wiki/42_(answer)
26 | //% "What is the Answer to the Ultimate Question of Life, the Universe, and Everything?"
27 | text: qsTrId("orn-dcm-question")
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/qml/pages/AboutPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | Page {
5 | allowedOrientations: defaultAllowedOrientations
6 |
7 | SilicaFlickable {
8 | anchors.fill: parent
9 | contentHeight: column.height + Theme.paddingMedium
10 |
11 | Column {
12 | id: column
13 | width: parent.width
14 | spacing: Theme.paddingMedium
15 |
16 | PageHeader {
17 | id: header
18 | //% "About Storeman"
19 | title: qsTrId("orn-about")
20 | }
21 |
22 | Item {
23 | height: icon.height + Theme.paddingMedium
24 | width: parent.width
25 |
26 | Image {
27 | id: icon
28 | anchors.horizontalCenter: parent.horizontalCenter
29 | source: "/usr/share/icons/hicolor/480x480/apps/harbour-storeman.png"
30 | }
31 | }
32 |
33 | Label {
34 | anchors.horizontalCenter: parent.horizontalCenter
35 | color: Theme.highlightColor
36 | text: applicationDisplayName + " " + Qt.application.version
37 | }
38 |
39 | Label {
40 | anchors {
41 | left: parent.left
42 | right: parent.right
43 | leftMargin: Theme.horizontalPageMargin
44 | rightMargin: Theme.horizontalPageMargin
45 | }
46 | height: implicitHeight + Theme.paddingMedium
47 | color: Theme.highlightColor
48 | linkColor: Theme.primaryColor
49 | font.pixelSize: Theme.fontSizeSmall
50 | wrapMode: Text.WordWrap
51 | horizontalAlignment: Qt.AlignHCenter
52 | //% "OpenRepos client application for SailfishOS
"
53 | //% "Storeman is Free Software (FLOSS), distributed under the terms of the MIT license .
"
54 | //% "Any issues (bug reports, feature suggestions, help requests etc.) shall be filed at GitHub (you may use the button below).
"
55 | text: qsTrId("orn-app-description-full").arg("https://github.com/storeman-developers/harbour-storeman/raw/master/LICENSE")
56 | onLinkActivated: Qt.openUrlExternally(link)
57 | }
58 |
59 | ButtonLayout {
60 | width: parent.width
61 |
62 | Button {
63 | text: "@GitHub"
64 | onClicked: Qt.openUrlExternally("https://github.com/storeman-developers")
65 | }
66 |
67 | Button {
68 | text: "@OpenRepos"
69 | onClicked: {
70 | pageStack.push(Qt.resolvedUrl("AppPage.qml"), {
71 | appId: 11621
72 | })
73 | }
74 | }
75 |
76 | Button {
77 | //% "Donation"
78 | text: qsTrId("orn-donation")
79 | onClicked: Qt.openUrlExternally("https://openrepos.net/donate")
80 | }
81 |
82 | Button {
83 | text: qsTrId("orn-translations")
84 | onClicked: pageStack.push(Qt.resolvedUrl("TranslationsPage.qml"))
85 | }
86 |
87 | Button {
88 | text: qsTrId("orn-development")
89 | onClicked: pageStack.push(Qt.resolvedUrl("DevelopmentPage.qml"))
90 | }
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/qml/pages/AuthorisationDialog.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 |
5 | Dialog {
6 | id: dialog
7 | objectName: "AuthorisationDialog"
8 | allowedOrientations: defaultAllowedOrientations
9 | canAccept: networkManager.connected &&
10 | usernameField.text &&
11 | usernameField.acceptableInput &&
12 | passwordField.acceptableInput
13 |
14 | onAccepted: OrnClient.login(usernameField.text, passwordField.text, savePasswordSwitch.checked)
15 |
16 | DialogHeader {
17 | id: header
18 | //% "Log in"
19 | acceptText: qsTrId("orn-login-action")
20 | }
21 |
22 | SilicaFlickable {
23 | anchors {
24 | left: parent.left
25 | top: header.bottom
26 | right: parent.right
27 | bottom: parent.bottom
28 | }
29 | clip: true
30 | contentHeight: content.height
31 |
32 | Column {
33 | id: content
34 | width: parent.width
35 |
36 | Label {
37 | anchors {
38 | left: parent.left
39 | right: parent.right
40 | leftMargin: Theme.horizontalPageMargin
41 | rightMargin: Theme.horizontalPageMargin
42 | }
43 | height: implicitHeight + Theme.paddingLarge
44 | verticalAlignment: Qt.AlignVCenter
45 | //% "Log in to OpenRepos.net to comment applications and "
46 | //% "reply to others comments. "
47 | //% "Storeman does not send your password to third-parties."
48 | text: qsTrId("orn-login-help")
49 | color: Theme.highlightColor
50 | font.pixelSize: Theme.fontSizeSmall
51 | wrapMode: Text.WordWrap
52 | }
53 |
54 | // Spacer
55 | Item {
56 | width: 1
57 | height: Theme.paddingMedium
58 | }
59 |
60 | TextField {
61 | id: usernameField
62 | width: parent.width
63 | //: A translated string should comprise less than 27 characters
64 | //% "Username"
65 | placeholderText: qsTrId("orn-username")
66 | label: placeholderText
67 | validator: RegExpValidator {
68 | regExp: new RegExp([
69 | /([a-zA-Z0-9_]{1,}|(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*/,
70 | /|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*/,
71 | /")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:/,
72 | /(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|/,
73 | /1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|/,
74 | /\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\]))/
75 | ].map(function(r) {return r.source}).join(''))
76 | }
77 | inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
78 |
79 | EnterKey.iconSource: "image://theme/icon-m-enter-next"
80 | EnterKey.enabled: text && acceptableInput
81 | EnterKey.onClicked: passwordField.forceActiveFocus()
82 |
83 | Component.onCompleted: text = OrnClient.userName
84 | }
85 |
86 | PasswordField {
87 | id: passwordField
88 | width: parent.width
89 | validator: RegExpValidator { regExp: /^.{1,}$/ }
90 |
91 | EnterKey.iconSource: "image://theme/icon-m-enter-close"
92 | EnterKey.onClicked: focus = false
93 | }
94 |
95 | TextSwitch {
96 | id: savePasswordSwitch
97 | width: parent.width
98 | //% "Save password"
99 | text: qsTrId("orn-save-password")
100 | //% "Save password to the encrypted device storage to perform automatic re-login."
101 | description: qsTrId("orn-save-password-help")
102 | }
103 | }
104 |
105 | VerticalScrollDecorator { }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/qml/pages/BackupDialog.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import QtQml 2.2
3 | import Sailfish.Silica 1.0
4 | import harbour.orn 1.0
5 | import "../components"
6 |
7 |
8 | Dialog {
9 | property OrnBackup backup
10 | // This is need for restoring options after orientation changed
11 | property QtObject backupOptions: QtObject {
12 | property string filename: "Storeman-%1.ini".arg(Qt.formatDateTime(new Date(), "yyyyMMddhhmmss"))
13 | property bool repos: true
14 | property bool installed: true
15 | property bool bookmarks: true
16 | }
17 |
18 | id: dialog
19 | allowedOrientations: defaultAllowedOrientations
20 | canAccept: loader.item.acceptable && (
21 | backupOptions.bookmarks ||
22 | backupOptions.repos ||
23 | backupOptions.installed
24 | )
25 |
26 | onAccepted: {
27 | var items = 0
28 | if (backupOptions.repos) {
29 | items = items | OrnBackup.BackupRepos
30 | }
31 | if (backupOptions.installed) {
32 | items = items | OrnBackup.BackupInstalled
33 | }
34 | if (backupOptions.bookmarks) {
35 | items = items | OrnBackup.BackupBookmarks
36 | }
37 | backup.backup(loader.item.path, items)
38 | }
39 |
40 | Component {
41 | id: portraitLayout
42 |
43 | SilicaFlickable {
44 | property alias path: options.path
45 | property alias acceptable: options.acceptable
46 |
47 | contentHeight: options.height + label.height
48 | clip: true
49 |
50 | BackupOptions {
51 | id: options
52 | }
53 |
54 | BackupLabel {
55 | id: label
56 | anchors {
57 | top: options.bottom
58 | topMargin: Theme.paddingLarge
59 | left: parent.left
60 | right: parent.right
61 | leftMargin: Theme.horizontalPageMargin
62 | rightMargin: Theme.horizontalPageMargin
63 | }
64 | }
65 |
66 | VerticalScrollDecorator { }
67 | }
68 | }
69 |
70 | Component {
71 | id: landscapeLayout
72 |
73 | Item {
74 | property alias path: options.path
75 | property alias acceptable: options.acceptable
76 |
77 | clip: true
78 |
79 | SilicaFlickable {
80 | anchors {
81 | top: parent.top
82 | right: parent.horizontalCenter
83 | bottom: parent.bottom
84 | left: parent.left
85 | }
86 | contentHeight: label.height
87 |
88 | BackupLabel {
89 | id: label
90 | anchors {
91 | left: parent.left
92 | right: parent.right
93 | margins: Theme.horizontalPageMargin
94 | }
95 | }
96 | }
97 |
98 | SilicaFlickable {
99 | anchors {
100 | top: parent.top
101 | right: parent.right
102 | bottom: parent.bottom
103 | left: parent.horizontalCenter
104 | }
105 | contentHeight: options.height
106 |
107 | BackupOptions {
108 | id: options
109 | }
110 | }
111 | }
112 | }
113 |
114 | DialogHeader {
115 | id: header
116 | //% "Backup"
117 | acceptText: qsTrId("orn-backup")
118 | }
119 |
120 | Loader {
121 | id: loader
122 | anchors {
123 | top: header.bottom
124 | right: parent.right
125 | bottom: parent.bottom
126 | left: parent.left
127 | }
128 |
129 | sourceComponent: dialog.isPortrait ? portraitLayout : landscapeLayout
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/qml/pages/BookmarksPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 | import "../components"
5 |
6 | Page {
7 | id: page
8 | allowedOrientations: defaultAllowedOrientations
9 |
10 | OrnBookmarksModel {
11 | id: bookmarksModel
12 | }
13 |
14 | Connections {
15 | target: bookmarksModel
16 | onRowsInserted: proxyModel.sort(Qt.AscendingOrder)
17 | }
18 |
19 | SilicaListView {
20 | id: bookmarksList
21 | anchors.fill: parent
22 | model: OrnProxyModel {
23 | id: proxyModel
24 | sortRole: OrnBookmarksModel.SortRole
25 | sortCaseSensitivity: Qt.CaseInsensitive
26 | sourceModel: bookmarksModel
27 | }
28 |
29 | header: PageHeader {
30 | //% "Bookmarks"
31 | title: qsTrId("orn-bookmarks")
32 | }
33 |
34 | section {
35 | property: "title"
36 | criteria: ViewSection.FirstCharacter
37 | delegate: SectionHeader {
38 | text: section.toUpperCase()
39 | }
40 | }
41 |
42 | delegate: AppListDelegate {}
43 |
44 | PullDownMenu {
45 | id: menu
46 |
47 | RefreshMenuItem {
48 | model: bookmarksModel
49 | }
50 |
51 | MenuSearchItem {}
52 |
53 | MenuStatusLabel {}
54 | }
55 |
56 | VerticalScrollDecorator {}
57 |
58 | BusyIndicator {
59 | size: BusyIndicatorSize.Large
60 | anchors.centerIn: parent
61 | running: bookmarksModel.fetching
62 | }
63 |
64 | ViewPlaceholder {
65 | enabled: !bookmarksModel.fetching && bookmarksList.count === 0
66 | //% "Your bookmarked applications will be shown here"
67 | text: qsTrId("orn-no-bookmarks")
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/qml/pages/CategoriesFilterPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 | import "../components"
5 |
6 | Page {
7 | allowedOrientations: defaultAllowedOrientations
8 |
9 | onStatusChanged: {
10 | if (status === PageStatus.Active) {
11 | categoriesList.model = categoriesModel
12 | }
13 | }
14 |
15 | OrnCategoriesModel {
16 | id: categoriesModel
17 | }
18 |
19 | Connections {
20 | target: networkManager
21 | onStateChanged: {
22 | if (categoriesList.count === 0 &&
23 | networkManager.connected) {
24 | categoriesModel.reset()
25 | }
26 | }
27 | }
28 |
29 | SilicaListView {
30 | id: categoriesList
31 | anchors.fill: parent
32 |
33 | header: PageHeader {
34 | //% "Categories filter"
35 | title: qsTrId("orn-categories-filter")
36 | //% "Select which categories to show"
37 | description: qsTrId("orn-categories-filter-descr")
38 | }
39 |
40 | delegate: CategoriesFilterDelegate {
41 | categoryVisible: model.visible
42 | height: Theme.itemSizeExtraSmall
43 | text: model.name
44 | depth: model.depth
45 | textAlignment: Text.AlignLeft
46 |
47 | onClicked: {
48 | const setVisible = !model.visible
49 | OrnClient.toggleCategoryVisibility(model.categoryId)
50 | for (var i in model.children) {
51 | OrnClient.setCategoryVisibility(model.children[i], setVisible)
52 | }
53 | }
54 | }
55 |
56 | PullDownMenu {
57 | MenuStatusLabel {}
58 | }
59 |
60 | VerticalScrollDecorator {}
61 |
62 | BusyIndicator {
63 | size: BusyIndicatorSize.Large
64 | anchors.centerIn: parent
65 | running: !viewPlaceholder.text &&
66 | categoriesList.count === 0
67 | }
68 |
69 | ViewPlaceholder {
70 | id: viewPlaceholder
71 | enabled: text
72 | text: networkManager.connected ? "" : qsTrId("orn-network-idle")
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/qml/pages/CategoriesPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 | import "../components"
5 |
6 | Page {
7 | allowedOrientations: defaultAllowedOrientations
8 |
9 | onStatusChanged: {
10 | if (status === PageStatus.Active) {
11 | categoriesList.model = categoriesModel
12 | }
13 | }
14 |
15 | OrnProxyModel {
16 | id: categoriesModel
17 | sourceModel: OrnCategoriesModel {}
18 | filterRole: OrnCategoriesModel.VisibilityRole
19 | }
20 |
21 | Connections {
22 | target: networkManager
23 | onStateChanged: {
24 | if (categoriesList.count === 0 &&
25 | networkManager.connected) {
26 | categoriesModel.reset()
27 | }
28 | }
29 | }
30 |
31 | SilicaListView {
32 | id: categoriesList
33 | anchors.fill: parent
34 |
35 | header: PageHeader {
36 | //% "Categories"
37 | title: qsTrId("orn-categories")
38 | }
39 |
40 | delegate: MoreButton {
41 | height: Theme.itemSizeExtraSmall
42 | text: model.name
43 | depth: model.depth
44 | textAlignment: Text.AlignLeft
45 |
46 | onClicked: pageStack.push(Qt.resolvedUrl("CategoryPage.qml"), {
47 | categoryId: model.categoryId,
48 | categoryName: model.name
49 | })
50 | }
51 |
52 | PullDownMenu {
53 | MenuSearchItem {}
54 | MenuStatusLabel {}
55 | }
56 |
57 | VerticalScrollDecorator {}
58 |
59 | BusyIndicator {
60 | size: BusyIndicatorSize.Large
61 | anchors.centerIn: parent
62 | running: !viewPlaceholder.text &&
63 | categoriesList.count === 0
64 | }
65 |
66 | ViewPlaceholder {
67 | id: viewPlaceholder
68 | enabled: text
69 | text: networkManager.connected ? "" : qsTrId("orn-network-idle")
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/qml/pages/CategoryPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 | import "../components"
5 |
6 | Page {
7 | property int categoryId
8 | property string categoryName
9 |
10 | allowedOrientations: defaultAllowedOrientations
11 |
12 | OrnAppsModel {
13 | id: categoryModel
14 | resource: "categories/%0/apps".arg(categoryId)
15 | onFetchingChanged: {
16 | if (!fetching && rowCount() === 0) {
17 | //% "Currently there are no apps in this category"
18 | viewPlaceholder.text = qsTrId("orn-category-noapps")
19 | }
20 | }
21 | }
22 |
23 | SilicaListView {
24 | id: categoryList
25 | anchors.fill: parent
26 |
27 | header: PageHeader {
28 | title: categoryName
29 | }
30 |
31 | model: networkManager.connected ? categoryModel : null
32 |
33 | delegate: AppListDelegate { }
34 |
35 | PullDownMenu {
36 | id: menu
37 |
38 | RefreshMenuItem {
39 | model: categoryModel
40 | }
41 |
42 | MenuSearchItem {}
43 |
44 | MenuStatusLabel {}
45 | }
46 |
47 | VerticalScrollDecorator {}
48 |
49 | BusyIndicator {
50 | size: BusyIndicatorSize.Large
51 | anchors.centerIn: parent
52 | running: !viewPlaceholder.enabled &&
53 | categoryList.count === 0 &&
54 | !menu.active
55 | }
56 |
57 | ViewPlaceholder {
58 | id: viewPlaceholder
59 | enabled: text
60 | text: {
61 | hintText = ""
62 | if (!networkManager.connected) {
63 | return qsTrId("orn-network-idle")
64 | }
65 | if (categoryModel.networkError) {
66 | hintText = qsTrId("orn-pull-refresh")
67 | return qsTrId("orn-network-error")
68 | }
69 | return ""
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/qml/pages/ChangelogPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import "../components"
4 |
5 | Page {
6 | property string appName
7 | property string appIconSource
8 | property alias changelog: label.text
9 |
10 | allowedOrientations: defaultAllowedOrientations
11 |
12 | SilicaFlickable {
13 | anchors.fill: parent
14 | contentHeight: content.height
15 |
16 | Column {
17 | id: content
18 | width: parent.width
19 |
20 | FancyPageHeader {
21 | id: header
22 | title: qsTrId("orn-changelog")
23 | description: appName
24 | iconSource: appIconSource
25 | }
26 |
27 | Label {
28 | id: label
29 | anchors {
30 | left: parent.left
31 | right: parent.right
32 | leftMargin: Theme.horizontalPageMargin
33 | rightMargin: Theme.horizontalPageMargin
34 | }
35 | color: Theme.primaryColor
36 | linkColor: Theme.highlightColor
37 | font.pixelSize: Theme.fontSizeSmall
38 | wrapMode: Text.WordWrap
39 | onLinkActivated: Qt.openUrlExternally(link)
40 | }
41 | }
42 |
43 | VerticalScrollDecorator { }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/qml/pages/DevelopmentPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import "../models"
4 | import "../components"
5 |
6 | Page {
7 | allowedOrientations: defaultAllowedOrientations
8 |
9 | SilicaListView {
10 | anchors.fill: parent
11 | model: DevelopersModel { }
12 |
13 | header: PageHeader {
14 | //% "Credits"
15 | title: qsTrId("orn-development")
16 | }
17 |
18 | delegate: Item {
19 | width: parent.width
20 | height: column.height + Theme.paddingLarge
21 |
22 | Column {
23 | id: column
24 | x: Theme.horizontalPageMargin
25 | width: parent.width - Theme.horizontalPageMargin * 2
26 | anchors.verticalCenter: parent.verticalCenter
27 | spacing: Theme.paddingSmall
28 |
29 | Label {
30 | width: parent.width
31 | wrapMode: Text.WordWrap
32 | horizontalAlignment: Qt.AlignHCenter
33 | color: Theme.highlightColor
34 | text: role
35 | }
36 |
37 | Column {
38 | width: parent.width
39 |
40 | Repeater {
41 | model: participants
42 | delegate: Label {
43 | width: parent.width
44 | wrapMode: Text.WordWrap
45 | horizontalAlignment: Qt.AlignHCenter
46 | color: Theme.secondaryColor
47 | linkColor: Theme.primaryColor
48 | font.pixelSize: Theme.fontSizeSmall
49 | text: link ? "%1 ".arg(link).arg(name) : name
50 | onLinkActivated: Qt.openUrlExternally(link)
51 | }
52 | }
53 | }
54 | }
55 | }
56 |
57 | PullDownMenu {
58 |
59 | MenuItem {
60 | //% "Source code & Issue tracker"
61 | text: qsTrId("orn-sources")
62 | onClicked: Qt.openUrlExternally("https://github.com/storeman-developers/harbour-storeman")
63 | }
64 |
65 | MenuStatusLabel { }
66 | }
67 |
68 | VerticalScrollDecorator { }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/qml/pages/ErrorPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | Page {
5 | property alias message: messageLabel.text
6 |
7 | objectName: "ErrorPage"
8 | allowedOrientations: defaultAllowedOrientations
9 |
10 | SilicaFlickable {
11 | anchors.fill: parent
12 | contentHeight: column.height
13 |
14 | Column {
15 | id: column
16 | width: parent.width
17 |
18 | PageHeader {
19 | title: qsTrId("orn-error")
20 | }
21 |
22 | Label {
23 | id: messageLabel
24 | anchors {
25 | left: parent.left
26 | right: parent.right
27 | leftMargin: Theme.horizontalPageMargin
28 | rightMargin: Theme.horizontalPageMargin
29 | }
30 | color: Theme.highlightColor
31 | wrapMode: Text.WordWrap
32 | }
33 | }
34 |
35 | VerticalScrollDecorator { }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/qml/pages/IntervalPickerDialog.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import "../components"
4 |
5 | Dialog {
6 | property alias hour: hoursView.currentIndex
7 | property alias minute: minutesView.currentIndex
8 |
9 | id: dialog
10 | canAccept: hour > 0 || minute >= 10
11 |
12 | DialogHeader {
13 | id: header
14 | //% "At least 10 minutes"
15 | title: qsTrId("orn-updates-check-interval-minimum")
16 | palette.highlightColor: canAccept ? Theme.highlightColor : Theme.errorColor
17 | }
18 |
19 | IntervalView {
20 | id: hoursView
21 | anchors {
22 | top: header.bottom
23 | right: parent.horizontalCenter
24 | bottom: parent.bottom
25 | left: parent.left
26 | bottomMargin: Theme.paddingLarge
27 | }
28 | model: 48
29 | }
30 |
31 | IntervalView {
32 | id: minutesView
33 | anchors {
34 | top: header.bottom
35 | right: parent.right
36 | bottom: parent.bottom
37 | left: parent.horizontalCenter
38 | bottomMargin: Theme.paddingLarge
39 | }
40 | model: 60
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/qml/pages/RecentAppsPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 | import "../components"
5 |
6 | Page {
7 | property OrnProxyModel model
8 |
9 | id: page
10 | allowedOrientations: defaultAllowedOrientations
11 |
12 | SilicaListView {
13 | id: appsList
14 | anchors.fill: parent
15 | model: networkManager.connected ? page.model : null
16 |
17 | header: PageHeader {
18 | //% "Recently updated"
19 | title: qsTrId("orn-recently-updated")
20 | }
21 |
22 | delegate: AppListDelegate { }
23 |
24 | section {
25 | property: "sinceUpdate"
26 | delegate: SectionHeader {
27 | text: section
28 | }
29 | }
30 |
31 | PullDownMenu {
32 | id: menu
33 |
34 | RefreshMenuItem {
35 | model: page.model
36 | }
37 |
38 | MenuSearchItem {}
39 |
40 | MenuStatusLabel {}
41 | }
42 |
43 | VerticalScrollDecorator {}
44 |
45 | BusyIndicator {
46 | size: BusyIndicatorSize.Large
47 | anchors.centerIn: parent
48 | running: !viewPlaceholder.text &&
49 | appsList.count === 0 &&
50 | !menu.active
51 | }
52 |
53 | ViewPlaceholder {
54 | id: viewPlaceholder
55 | enabled: text
56 | text: {
57 | hintText = ""
58 | if (!networkManager.connected) {
59 | //% "Network is unavailable"
60 | return qsTrId("orn-network-idle")
61 | }
62 | if (page.model.sourceModel.networkError) {
63 | //% "Pull down to refresh"
64 | hintText = qsTrId("orn-pull-refresh")
65 | //% "A network error occurred"
66 | return qsTrId("orn-network-error")
67 | }
68 | return ""
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/qml/pages/RepositoryPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 | import "../components"
5 |
6 | Page {
7 | property int userId
8 | property string userName
9 | property string userIcon
10 | property int previousAppId: -1
11 |
12 | id: page
13 | allowedOrientations: defaultAllowedOrientations
14 |
15 | SilicaListView {
16 | id: appsList
17 | anchors.fill: parent
18 |
19 | header: FancyPageHeader {
20 | id: header
21 | //% "Repository"
22 | title: qsTrId("orn-repository")
23 | description: userName
24 | iconSource: userIcon
25 | }
26 |
27 | model: OrnProxyModel {
28 | id: proxyModel
29 | sortRole: OrnAppsModel.SortRole
30 | filterRole: OrnAppsModel.VisibilityRole
31 | sourceModel: OrnAppsModel {
32 | id: appsModel
33 | fetchable: false
34 | resource: "users/%0/apps".arg(userId)
35 | onRowsInserted: proxyModel.sort(Qt.AscendingOrder)
36 | }
37 | }
38 |
39 | delegate: AppListDelegate {
40 | returnToUser: true
41 | showUser: false
42 | previousAppId: page.previousAppId
43 | }
44 |
45 | PullDownMenu {
46 | id: menu
47 |
48 | RefreshMenuItem {
49 | model: appsModel
50 | }
51 |
52 | MenuSearchItem {}
53 |
54 | MenuStatusLabel {}
55 | }
56 |
57 | VerticalScrollDecorator {}
58 |
59 | BusyIndicator {
60 | size: BusyIndicatorSize.Large
61 | anchors.centerIn: parent
62 | running: !viewPlaceholder.text &&
63 | appsList.count === 0 &&
64 | !menu.active
65 | }
66 |
67 | ViewPlaceholder {
68 | id: viewPlaceholder
69 | enabled: text
70 | text: {
71 | hintText = ""
72 | if (!networkManager.connected) {
73 | return qsTrId("orn-network-idle")
74 | }
75 | if (appsModel.networkError) {
76 | hintText = qsTrId("orn-pull-refresh")
77 | return qsTrId("orn-network-error")
78 | }
79 | return ""
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/qml/pages/RestoreDialog.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import QtQml 2.2
3 | import Sailfish.Silica 1.0
4 | import harbour.orn 1.0
5 |
6 | Dialog {
7 | property OrnBackup backup
8 | property string filePath
9 | readonly property var _details: backup.details(filePath)
10 |
11 | id: dialog
12 | allowedOrientations: defaultAllowedOrientations
13 | canAccept: networkManager.connected
14 |
15 | onAccepted: backup.restore(filePath)
16 |
17 | DialogHeader {
18 | id: header
19 | //% "Restore from a file"
20 | title: qsTrId("orn-restore-title")
21 | //% "Restore"
22 | acceptText: qsTrId("orn-restore")
23 | }
24 |
25 | SilicaFlickable {
26 | anchors {
27 | top: header.bottom
28 | left: parent.left
29 | right: parent.right
30 | bottom: parent.bottom
31 | }
32 | contentHeight: column.height
33 |
34 | Column {
35 | id: column
36 | width: parent.width
37 |
38 | Label {
39 | anchors {
40 | left: parent.left
41 | right: parent.right
42 | margins: Theme.horizontalPageMargin
43 | }
44 | color: Theme.highlightColor
45 | wrapMode: Text.WordWrap
46 | font.pixelSize: Theme.fontSizeSmall
47 | //% "Restore OpenRepos repositories and installed apps from the selected file. This action will not affect your current repositories and will not remove installed applications."
48 | text: qsTrId("orn-restore-hint")
49 | }
50 |
51 | SectionHeader {
52 | //% "Details"
53 | text: qsTrId("orn-details")
54 | }
55 |
56 | DetailItem {
57 | //% "Created"
58 | label: qsTrId("orn-created")
59 | value: _details.created.toLocaleString(Qt.locale(), Locale.NarrowFormat)
60 | }
61 |
62 | DetailItem {
63 | //% "Total repositories"
64 | label: qsTrId("orn-total-repos")
65 | value: _details.repos
66 | }
67 |
68 | DetailItem {
69 | //% "Installed packages"
70 | label: qsTrId("orn-installed-packages")
71 | value: _details.packages
72 | }
73 |
74 | DetailItem {
75 | label: qsTrId("orn-bookmarks")
76 | value: _details.bookmarks
77 | }
78 | }
79 |
80 | VerticalScrollDecorator { }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/qml/pages/ScreenshotPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 |
4 | FullscreenContentPage {
5 | property alias model: slideshow.model
6 | property alias currentIndex: slideshow.currentIndex
7 |
8 | id: page
9 | allowedOrientations: defaultAllowedOrientations
10 |
11 | SlideshowView {
12 | id: slideshow
13 | anchors.fill: parent
14 |
15 | delegate: Item {
16 | width: slideshow.width
17 | height: slideshow.height
18 |
19 | BusyIndicator {
20 | anchors.centerIn: parent
21 | size: BusyIndicatorSize.Large
22 | running: image.status === Image.Loading
23 | color: Theme.highlightColor
24 | }
25 |
26 | Image {
27 | id: image
28 | anchors.fill: parent
29 | source: modelData.url
30 | fillMode: Image.PreserveAspectFit
31 | opacity: status === Image.Ready ? 1.0 : 0.0
32 |
33 | Behavior on opacity { FadeAnimation { } }
34 | }
35 |
36 | MouseArea {
37 | anchors.fill: parent
38 | onClicked: pageStack.pop()
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/qml/pages/SearchPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 | import "../components"
5 |
6 | Page {
7 | property string initialSearch
8 |
9 | function reset() {
10 | searchModel.searchKey = ""
11 | //% "Search results will be shown here"
12 | viewPlaceholder.text = qsTrId("orn-searchpage-placeholder-default")
13 | //% "Type some keywords in the field above"
14 | viewPlaceholder.hintText = qsTrId("orn-searchpage-placeholder-default-hint")
15 | view.headerItem.searchField.forceActiveFocus()
16 | }
17 |
18 | function _search(text) {
19 | searchModel.searchKey = text
20 | viewPlaceholder.text = ""
21 | viewPlaceholder.hintText = ""
22 | forceActiveFocus()
23 | }
24 |
25 | id: page
26 | allowedOrientations: defaultAllowedOrientations
27 |
28 | onStatusChanged: {
29 | if (status === PageStatus.Active) {
30 | if (initialSearch) {
31 | view.headerItem.searchField.text = initialSearch
32 | _search(initialSearch)
33 | } else if (!view.headerItem.searchField.text) {
34 | reset()
35 | }
36 | }
37 | }
38 |
39 | SilicaListView
40 | {
41 | id: view
42 | anchors.fill: parent
43 | model: OrnProxyModel {
44 | id: proxyModel
45 | sourceModel: OrnSearchAppsModel {
46 | id: searchModel
47 | onFetchingChanged: {
48 | if (!fetching && proxyModel.rowCount() === 0) {
49 | //% "Nothing found"
50 | viewPlaceholder.text = qsTrId("orn-searchpage-placeholder-noresults")
51 | //% "Try to change search keywords"
52 | viewPlaceholder.hintText = qsTrId("orn-searchpage-placeholder-noresults-hint")
53 | }
54 | }
55 | }
56 | filterRole: OrnSearchAppsModel.VisibilityRole
57 | }
58 |
59 | header: Column {
60 | property alias searchField: searchField
61 |
62 | width: parent.width
63 |
64 | PageHeader {
65 | //: The search menu item and the search page header text - should be a noun
66 | //% "Search"
67 | title: qsTrId("orn-search")
68 | }
69 |
70 | SearchField {
71 | id: searchField
72 | width: parent.width
73 | //: The search field placeholder text - should be a verb
74 | //% "Search"
75 | placeholderText: qsTrId("orn-searchfield-placeholder")
76 |
77 | EnterKey.enabled: text.length > 0
78 | EnterKey.iconSource: "image://theme/icon-m-enter-accept"
79 | EnterKey.onClicked: _search(text)
80 |
81 | onTextChanged: if (!text) reset()
82 | }
83 | }
84 |
85 | delegate: AppListDelegate { }
86 |
87 | VerticalScrollDecorator { }
88 |
89 | ViewPlaceholder {
90 | id: viewPlaceholder
91 | enabled: text
92 | }
93 |
94 | BusyIndicator {
95 | id: busyIndicator
96 | size: BusyIndicatorSize.Large
97 | anchors.centerIn: parent
98 | running: parent.count === 0 && !viewPlaceholder.enabled
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/qml/pages/SharePage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import Sailfish.TransferEngine 1.0
4 |
5 | SharePage {
6 | id: page
7 |
8 | property string link
9 | property string linkTitle
10 |
11 | //% "Share link"
12 | header: qsTrId("orn-share-link")
13 | mimeType: "text/x-url"
14 | showAddAccount: false
15 | content: {
16 | "type": "text/x-url",
17 | "status": page.link,
18 | "linkTitle": page.linkTitle
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/qml/pages/TagAppsPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 | import "../components"
5 |
6 | Page {
7 | property int tagId
8 | property string tagName
9 | property int previousAppId: -1
10 |
11 | id: page
12 | allowedOrientations: defaultAllowedOrientations
13 |
14 | SilicaListView {
15 | id: appsList
16 | anchors.fill: parent
17 |
18 | model: OrnProxyModel {
19 | id: proxyModel
20 | sortRole: OrnAppsModel.SortRole
21 | filterRole: OrnAppsModel.VisibilityRole
22 | sourceModel: OrnAppsModel {
23 | id: appsModel
24 | resource: tagId !== 0 ? "tags/%0/apps".arg(tagId) : ""
25 | onRowsInserted: proxyModel.sort(Qt.AscendingOrder)
26 | }
27 | }
28 |
29 | header: PageHeader {
30 | id: header
31 | //% "Tagged Applications"
32 | title: qsTrId("orn-tag-apps")
33 | description: tagName
34 | }
35 |
36 | delegate: AppListDelegate {
37 | previousAppId: page.previousAppId
38 | previousStep: 2
39 | }
40 |
41 | PullDownMenu {
42 | id: menu
43 |
44 | RefreshMenuItem {
45 | model: appsModel
46 | }
47 |
48 | MenuStatusLabel { }
49 | }
50 |
51 | VerticalScrollDecorator { }
52 |
53 | BusyIndicator {
54 | size: BusyIndicatorSize.Large
55 | anchors.centerIn: parent
56 | running: !viewPlaceholder.text &&
57 | appsList.count === 0 &&
58 | !menu.active
59 | }
60 |
61 | ViewPlaceholder {
62 | id: viewPlaceholder
63 | enabled: text
64 | text: {
65 | hintText = ""
66 | if (!networkManager.connected) {
67 | return qsTrId("orn-network-idle")
68 | }
69 | if (appsModel.networkError) {
70 | hintText = qsTrId("orn-pull-refresh")
71 | return qsTrId("orn-network-error")
72 | }
73 | return ""
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/qml/pages/TagsPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 | import "../components"
5 |
6 |
7 | Page {
8 | property alias tagIds: tagsModel.tagIds
9 | property string appName
10 | property string appIconSource
11 | property int previousAppId: -1
12 |
13 | id: page
14 | allowedOrientations: defaultAllowedOrientations
15 |
16 | SilicaListView {
17 | id: tagsList
18 | anchors.fill: parent
19 | model: OrnProxyModel {
20 | id: proxyModel
21 | sortRole: OrnTagsModel.AppsCountRole
22 | sourceModel: OrnTagsModel {
23 | id: tagsModel
24 | onRowsInserted: proxyModel.sort(Qt.DescendingOrder)
25 | }
26 | }
27 |
28 | header: FancyPageHeader {
29 | id: header
30 | //% "Tags"
31 | title: qsTrId("orn-tags")
32 | description: appName
33 | iconSource: appIconSource
34 | }
35 |
36 | delegate: TagDelegate { }
37 |
38 | PullDownMenu {
39 | id: menu
40 |
41 | RefreshMenuItem {
42 | model: tagsModel
43 | }
44 |
45 | MenuStatusLabel { }
46 | }
47 |
48 | VerticalScrollDecorator { }
49 |
50 | BusyIndicator {
51 | size: BusyIndicatorSize.Large
52 | anchors.centerIn: parent
53 | running: tagsModel.fetching && !menu.active
54 | }
55 |
56 | ViewPlaceholder {
57 | enabled: text
58 | text: {
59 | hintText = ""
60 | if (!networkManager.connected) {
61 | return qsTrId("orn-network-idle")
62 | }
63 | if (tagsModel.networkError) {
64 | hintText = qsTrId("orn-pull-refresh")
65 | return qsTrId("orn-network-error")
66 | }
67 | return ""
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/qml/pages/TranslationsPage.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import "../components"
4 | import "../models"
5 |
6 | Page {
7 | allowedOrientations: defaultAllowedOrientations
8 |
9 | SilicaListView {
10 | anchors.fill: parent
11 | model: TranslatorsModel { }
12 |
13 | header: PageHeader {
14 | //% "Translations"
15 | title: qsTrId("orn-translations")
16 | }
17 |
18 | delegate: Item {
19 | width: parent.width
20 | height: column.height + Theme.paddingLarge
21 |
22 | Column {
23 | id: column
24 | x: Theme.horizontalPageMargin
25 | width: parent.width - Theme.horizontalPageMargin * 2
26 | anchors.verticalCenter: parent.verticalCenter
27 | spacing: Theme.paddingSmall
28 |
29 | Label {
30 | width: parent.width
31 | wrapMode: Text.WordWrap
32 | horizontalAlignment: Qt.AlignHCenter
33 | color: Theme.highlightColor
34 | text: {
35 | var l = Qt.locale(locale)
36 | var res = l.nativeLanguageName
37 | res = res.charAt(0).toUpperCase() + res.slice(1)
38 | if (locale.length > 2) {
39 | res += " (%0)".arg(l.nativeCountryName)
40 | }
41 | return res
42 | }
43 | }
44 |
45 | ParticipantsDelegate {
46 | //% "Coordinators"
47 | title: qsTrId("orn-coordinators")
48 | model: coordinators
49 | }
50 |
51 | ParticipantsDelegate {
52 | //% "Translators"
53 | title: qsTrId("orn-translators")
54 | model: translators
55 | }
56 |
57 | ParticipantsDelegate {
58 | //% "Reviewers"
59 | title: qsTrId("orn-reviewers")
60 | model: reviewers
61 | }
62 | }
63 | }
64 |
65 | PullDownMenu {
66 |
67 | MenuItem {
68 | text: "Storeman@Transifex"
69 | onClicked: Qt.openUrlExternally("https://app.transifex.com/mentaljam/harbour-storeman")
70 | }
71 |
72 | MenuStatusLabel { }
73 | }
74 |
75 | VerticalScrollDecorator { }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/qml/pages/UnusedReposDialog.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.0
2 | import Sailfish.Silica 1.0
3 | import harbour.orn 1.0
4 |
5 | Dialog {
6 | property alias repos: listView.model
7 | property var _reposToRemove: repos.reduce(function(res, author) {
8 | res[author] = true
9 | return res
10 | }, {})
11 |
12 | function _setRepo(author, remove) {
13 | if (remove) {
14 | _reposToRemove[author] = remove
15 | } else {
16 | delete _reposToRemove[author]
17 | }
18 | }
19 |
20 | id: dialog
21 | allowedOrientations: defaultAllowedOrientations
22 |
23 | Component.onCompleted: console.log(_reposToRemove)
24 |
25 | onAccepted: Object.keys(_reposToRemove).forEach(removeAuthorRepo)
26 |
27 | SilicaListView {
28 | id: listView
29 | anchors.fill: parent
30 |
31 | header: Column {
32 | width: parent.width
33 |
34 | DialogHeader {
35 | id: header
36 | acceptText: qsTrId("orn-remove")
37 | //% "Unused repositories"
38 | title: qsTrId("orn-unused-repos")
39 | }
40 |
41 | Label {
42 | anchors {
43 | left: parent.left
44 | right: parent.right
45 | leftMargin: Theme.horizontalPageMargin
46 | rightMargin: Theme.horizontalPageMargin
47 | }
48 | color: Theme.highlightColor
49 | wrapMode: Text.WordWrap
50 | textFormat: Text.RichText
51 | //% "There are no installed packages for the next repositories.
"
52 | //% "Do you want to remove them now?
"
53 | text: qsTrId("orn-unused-repos-text")
54 | }
55 | }
56 |
57 | delegate: TextSwitch {
58 | checked: true
59 | text: modelData
60 | onCheckedChanged: _setRepo(modelData, checked)
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/qml/qmldir:
--------------------------------------------------------------------------------
1 | module harbour.orn
2 | singleton StoremanStyles 1.0 StoremanStyles.qml
3 |
--------------------------------------------------------------------------------
/rpm/harbour-storeman.rpmlintrc:
--------------------------------------------------------------------------------
1 | # References: An exceptionally comprehensive example rpmlintrc file
2 | # https://github.com/coreos/tectonic-rpms/blob/master/rpmlint-config
3 | # but adheres to the old syntax, not the new TOML one: https://toml.io/en/
4 | # See also https://fedoraproject.org/wiki/Common_Rpmlint_issues and
5 | # https://en.opensuse.org/openSUSE:Packaging_checks#Building_Packages_in_spite_of_errors
6 |
7 | # On behalf of Jolla's tar_git / SailfishOS-OBS:
8 | # - It re-writes the DistURL, rendering it inconsistent
9 | addFilter('invalid-url DistURL obs:')
10 | # - It has a limited list of FLOSS-licenses, most SDPX-IDs are missing
11 | addFilter('invalid-license')
12 | # - It extracts strange changelog entries out of Git, if a %%changelog section is used
13 | addFilter('incoherent-version-in-changelog')
14 | # - It sometimes re-writes the %version-%release strings of package names,
15 | # when referencing (only) a branch (i.e., not a git tag), for example,
16 | # 0.5.2-1 to 0.5.2+main.20230129011931.1.g584263a-1.8.1.jolla
17 | addFilter('filename-too-long-for-joliet')
18 |
19 | # On behalf of the SailfishOS:Chum metadata definition:
20 | # - which often forces one to do
21 | addFilter('description-line-too-long')
22 | setBadness('description-line-too-long', 0)
23 |
24 | # On our own behalf:
25 | # - This is how it ought to be
26 | addFilter('obsolete-not-provided harbour-storeman-installer')
27 | addFilter('unversioned Obsoletes: Obsoletes: *harbour-storeman-installer')
28 | # - This is also how it ought to be
29 | addFilter('dangerous-command-in-%post[un]* rm')
30 |
31 |
--------------------------------------------------------------------------------
/scripts/update_categories.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from urllib import request
4 | import json
5 | import re
6 |
7 | URL = 'https://openrepos.net/api/v1/categories'
8 | PATTERN = re.compile(r'[ &]+')
9 |
10 | def process(json_array):
11 | for o in json_array:
12 | tid = o['tid'].rjust(4)
13 | name = o['name']
14 | trid = PATTERN.sub('-', name).lower()
15 | print(f'//% "{name}"\n{{ {tid}, QT_TRID_NOOP("orn-cat-{trid}") }},')
16 | if 'childrens' in o:
17 | process(o['childrens'])
18 |
19 | if __name__ == '__main__':
20 | response = request.urlopen(URL)
21 | categories = json.loads(response.read().decode('UTF-8'))
22 | process(categories)
23 |
--------------------------------------------------------------------------------
/scripts/update_translators.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from os import path
4 | import sys
5 | import configparser
6 | from getpass import getpass
7 | import requests
8 | import json
9 |
10 | # This API is deprectated and does not work any longer:
11 | URL = 'http://www.transifex.com/api/2/project/harbour-storeman/languages/'
12 | # Either use Transifex's Python library: https://developers.transifex.com/reference/api-python-sdk
13 | # Or use Transifex's REST API: https://developers.transifex.com/reference/get_languages
14 |
15 | def credentials():
16 | '''Get Transifex user name and password'''
17 | trc_path = path.join(path.expanduser('~'), '.transifexrc')
18 | if path.isfile(trc_path):
19 | print('Fetching credentials from', trc_path)
20 | trc = configparser.ConfigParser()
21 | try:
22 | trc.read(trc_path)
23 | section = trc['https://www.transifex.com']
24 | return section['username'], section['password']
25 | except Exception as e:
26 | print('Error reading .transifexrc:', e)
27 | sys.exit(1)
28 | else:
29 | return (
30 | input('Transifex username: '),
31 | getpass(prompt='Transifex password: ')
32 | )
33 |
34 | def participants(writer, name, tr):
35 | writer.write(f' {name}: [')
36 | arr = tr[name]
37 | last_i = len(arr) - 1
38 | for i, p in enumerate(arr):
39 | writer.write(f'\n ListElement {{ name: "{p}" }}')
40 | writer.write(',' if i < last_i else '\n ')
41 | writer.write(']\n')
42 |
43 | if __name__ == '__main__':
44 | auth = credentials()
45 | print('Fetching', URL)
46 | data = requests.get(URL, auth=auth).content
47 | data = json.loads(data.decode('UTF-8'))
48 | model_file = path.normpath(path.dirname(__file__) + '/../qml/models/TranslatorsModel.qml')
49 | print('Writing', model_file)
50 | with open(model_file, 'w', encoding='UTF-8') as writer:
51 | writer.write('import QtQuick 2.0\n\nListModel {')
52 | for tr in data:
53 | writer.write('\n ListElement {\n')
54 | writer.write(f' locale: "{tr["language_code"]}"\n')
55 | participants(writer, 'coordinators', tr)
56 | participants(writer, 'translators', tr)
57 | participants(writer, 'reviewers', tr)
58 | writer.write(' }\n')
59 | writer.write('}\n')
60 |
--------------------------------------------------------------------------------
/src/harbour-storeman.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #include "ornclient.h"
8 | #include "ornpm.h"
9 | #include "ornapplication.h"
10 | #include "ornappsmodel.h"
11 | #include "ornsearchappsmodel.h"
12 | #include "ornrepomodel.h"
13 | #include "orninstalledappsmodel.h"
14 | #include "ornproxymodel.h"
15 | #include "orncommentsmodel.h"
16 | #include "orncategoriesmodel.h"
17 | #include "orntagsmodel.h"
18 | #include "ornbookmarksmodel.h"
19 | #include "ornbackup.h"
20 | #include "networkaccessmanagerfactory.h"
21 | #include "storeman.h"
22 | #include
23 |
24 |
25 | void registerTypes()
26 | {
27 | #ifndef QT_DEBUG
28 | auto uri = "harbour.orn";
29 | #else
30 | # define uri "harbour.orn"
31 | #endif
32 |
33 | qmlRegisterType (uri, 1, 0, "OrnApplication");
34 | qmlRegisterType (uri, 1, 0, "OrnAppsModel");
35 | qmlRegisterType (uri, 1, 0, "OrnSearchAppsModel");
36 | qmlRegisterType (uri, 1, 0, "OrnRepoModel");
37 | qmlRegisterType(uri, 1, 0, "OrnInstalledAppsModel");
38 | qmlRegisterType (uri, 1, 0, "OrnProxyModel");
39 | qmlRegisterType (uri, 1, 0, "OrnCommentsModel");
40 | qmlRegisterType (uri, 1, 0, "OrnCategoriesModel");
41 | qmlRegisterType (uri, 1, 0, "OrnTagsModel");
42 | qmlRegisterType (uri, 1, 0, "OrnBookmarksModel");
43 | qmlRegisterType (uri, 1, 0, "OrnBackup");
44 |
45 | qmlRegisterSingletonType (uri, 1, 0, "OrnClient", OrnClient::qmlInstance);
46 | qmlRegisterSingletonType (uri, 1, 0, "OrnPm", OrnPm::qmlInstance);
47 | qmlRegisterSingletonType (uri, 1, 0, "Storeman", Storeman::qmlInstance);
48 |
49 | qRegisterMetaType>();
50 | qRegisterMetaType>();
51 | }
52 |
53 | int main(int argc, char *argv[])
54 | {
55 | registerTypes();
56 |
57 | SailfishApp::application(argc, argv);
58 | QGuiApplication::setApplicationVersion(QStringLiteral(STOREMAN_VERSION));
59 | // QGuiApplication::setApplicationDisplayName(QStringLiteral("Storeman")); (available in Qt.application object since Qt 5.9) cannot be used,
60 | // because SFOS 4.4.0 still deploys QT 5.6 and Storeman supports SFOS ≥ 3.1.0!
61 | // For a detailed discussion see https://github.com/storeman-developers/harbour-storeman/issues/356#issuecomment-1192249781
62 |
63 | auto view = SailfishApp::createView();
64 |
65 | NetworkAccessManagerFactory factory;
66 | view->engine()->setNetworkAccessManagerFactory(&factory);
67 |
68 | view->setSource(SailfishApp::pathToMainQml());
69 | view->show();
70 |
71 | return QGuiApplication::exec();
72 | }
73 |
--------------------------------------------------------------------------------
/src/networkaccessmanagerfactory.cpp:
--------------------------------------------------------------------------------
1 | #include "networkaccessmanagerfactory.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 |
9 | QNetworkAccessManager *NetworkAccessManagerFactory::create(QObject *parent)
10 | {
11 | auto manager = new QNetworkAccessManager(parent);
12 | auto diskCache = new QNetworkDiskCache(manager);
13 | diskCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
14 | .append("/network"));
15 | qDebug() << "Setting qml network cache dir to" << diskCache->cacheDirectory();
16 | manager->setCache(diskCache);
17 | return manager;
18 | }
19 |
--------------------------------------------------------------------------------
/src/networkaccessmanagerfactory.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
6 | {
7 | public:
8 | NetworkAccessManagerFactory() = default;
9 |
10 | // QQmlNetworkAccessManagerFactory interface
11 | public:
12 | QNetworkAccessManager *create(QObject *parent) override;
13 | };
14 |
--------------------------------------------------------------------------------
/src/ornapplistitem.cpp:
--------------------------------------------------------------------------------
1 | #include "ornapplistitem.h"
2 | #include "orncategorylistitem.h"
3 | #include "ornutils.h"
4 | #include "ornconst.h"
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 |
12 | QString sinceLabel(quint32 value)
13 | {
14 | auto curDate = QDate::currentDate();
15 | auto date = QDateTime::fromMSecsSinceEpoch(qint64(value) * 1000).date();
16 | auto days = date.daysTo(curDate);
17 | if (days == 0)
18 | {
19 | //% "Today"
20 | return qtTrId("orn-today");
21 | }
22 | if (days == 1)
23 | {
24 | //% "Yesterday"
25 | return qtTrId("orn-yesterday");
26 | }
27 | if (days < 7 && date.dayOfWeek() < curDate.dayOfWeek())
28 | {
29 | //% "This week"
30 | return qtTrId("orn-this-week");
31 | }
32 | if (days < curDate.daysInMonth() && date.day() < curDate.day())
33 | {
34 | //% "This month"
35 | return qtTrId("orn-this-month");
36 | }
37 | //: Output format for the month and year - %0 is a long month name and %1 is a year (for example "May 2017")
38 | //% "%0 %1"
39 | return qtTrId("orn-month-format").arg(
40 | QDate::longMonthName(date.month(), QDate::StandaloneFormat)).arg(date.year());
41 | }
42 |
43 | OrnAppListItem::OrnAppListItem(const QJsonObject &data)
44 | : valid(data.size() > 1)
45 | , appId(data[OrnConst::appid].toVariant().toUInt())
46 | , title(OrnUtils::toString(data[OrnConst::title]))
47 | , iconSource(OrnUtils::toString(data[OrnConst::icon].toObject()[OrnConst::url]))
48 | {
49 | auto created = OrnUtils::toUint(data[OrnConst::created]);
50 | if (created > 0)
51 | {
52 | createDate.setMSecsSinceEpoch(qint64(created) * 1000);
53 | sinceUpdate = sinceLabel(created);
54 | }
55 |
56 | auto ratingobj = data[OrnConst::rating].toObject();
57 | ratingCount = OrnUtils::toUint(ratingobj[OrnConst::count]);
58 | rating = ratingobj[OrnConst::rating].toString().toFloat();
59 |
60 | userName = OrnUtils::toString(data[OrnConst::user].toObject()[OrnConst::name]);
61 |
62 | auto categories = data[OrnConst::category].toArray();
63 | auto tid = OrnUtils::toUint(categories.last().toObject()[OrnConst::tid]);
64 | category = OrnCategoryListItem::categoryName(tid);
65 | categoryId = tid;
66 |
67 | package = OrnUtils::toString(data[OrnConst::package].toObject()[OrnConst::name]);
68 |
69 | if (iconSource.isEmpty() ||
70 | iconSource.endsWith(QStringLiteral("icon-defaultpackage.png")))
71 | {
72 | iconSource = QStringLiteral("image://theme/icon-launcher-default");
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ornapplistitem.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | class QJsonObject;
7 |
8 | struct OrnAppListItem
9 | {
10 | OrnAppListItem(const QJsonObject &data);
11 |
12 | bool valid;
13 | quint32 appId;
14 | quint32 ratingCount;
15 | float rating;
16 | QString title;
17 | QString userName;
18 | QString iconSource;
19 | QString sinceUpdate;
20 | QString category;
21 | quint32 categoryId;
22 | QString package;
23 | QDateTime createDate;
24 | };
25 |
--------------------------------------------------------------------------------
/src/ornappsmodel.cpp:
--------------------------------------------------------------------------------
1 | #include "ornappsmodel.h"
2 | #include "ornapplistitem.h"
3 | #include "ornpm.h"
4 | #include "ornclient.h"
5 |
6 |
7 | OrnAppsModel::OrnAppsModel(bool fetchable, QObject *parent)
8 | : OrnAbstractListModel(fetchable, parent)
9 | {
10 | connect(OrnPm::instance(), &OrnPm::packageStatusChanged,
11 | this, [this](const QString &packageName, int status)
12 | {
13 | Q_UNUSED(status)
14 |
15 | auto size = mData.size();
16 | for (size_t i = 0; i < size; ++i)
17 | {
18 | if (mData[i].package == packageName)
19 | {
20 | auto ind = this->createIndex(int(i), 0);
21 | emit this->dataChanged(ind, ind, {PackageStatusRole});
22 | return;
23 | }
24 | }
25 | });
26 |
27 | connect(OrnClient::instance(), &OrnClient::bookmarkChanged,
28 | this, [this](quint32 appid, bool bookmarked)
29 | {
30 | Q_UNUSED(bookmarked)
31 |
32 | for (size_t i = 0, size = mData.size(); i < size; ++i)
33 | {
34 | if (mData[i].appId == appid)
35 | {
36 | auto ind = this->createIndex(int(i), 0);
37 | emit this->dataChanged(ind, ind, {BookmarkRole});
38 | return;
39 | }
40 | }
41 | });
42 |
43 | connect(OrnClient::instance(), &OrnClient::categoryVisibilityChanged,
44 | this, [this](quint32 categoryId, bool visible)
45 | {
46 | Q_UNUSED(visible)
47 |
48 | for (size_t i = 0, size = mData.size(); i < size; ++i)
49 | {
50 | if (mData[i].categoryId == categoryId)
51 | {
52 | auto ind = this->createIndex(int(i), 0);
53 | emit this->dataChanged(ind, ind, {VisibilityRole});
54 | return;
55 | }
56 | }
57 | });
58 | }
59 |
60 | void OrnAppsModel::setFetchable(bool fetchable)
61 | {
62 | if (mFetchable != fetchable)
63 | {
64 | mFetchable = fetchable;
65 | emit this->fetchableChanged();
66 | this->reset();
67 | }
68 | }
69 |
70 | void OrnAppsModel::setResource(const QString &resource)
71 | {
72 | if (mResource != resource)
73 | {
74 | mResource = resource;
75 | emit this->resourceChanged();
76 | this->reset();
77 | }
78 | }
79 |
80 | QVariant OrnAppsModel::data(const QModelIndex &index, int role) const
81 | {
82 | if (!index.isValid())
83 | {
84 | return QVariant();
85 | }
86 |
87 | const auto &app = mData[size_t(index.row())];
88 | switch (role)
89 | {
90 | case SortRole:
91 | return app.title.toLower();
92 | case ValidityRole:
93 | return app.valid;
94 | case BookmarkRole:
95 | return OrnClient::instance()->hasBookmark(app.appId);
96 | case PackageStatusRole:
97 | return OrnPm::instance()->packageStatus(app.package);
98 | case AppIdRole:
99 | return app.appId;
100 | case CreateDateRole:
101 | return app.createDate;
102 | case RatingCountRole:
103 | return app.ratingCount;
104 | case RatingRole:
105 | return app.rating;
106 | case TitleRole:
107 | return app.title;
108 | case UserNameRole:
109 | return app.userName;
110 | case IconSourceRole:
111 | return app.iconSource;
112 | case SinceUpdateRole:
113 | return app.sinceUpdate;
114 | case CategoryRole:
115 | return app.category;
116 | case CategoryIdRole:
117 | return app.categoryId;
118 | case VisibilityRole:
119 | return OrnClient::instance()->categoryVisible(app.categoryId);
120 | default:
121 | return QVariant();
122 | }
123 | }
124 |
125 | QHash OrnAppsModel::roleNames() const
126 | {
127 | return {
128 | { ValidityRole, "isValid" },
129 | { BookmarkRole, "isBookmarked" },
130 | { PackageStatusRole, "packageStatus" },
131 | { AppIdRole, "appId" },
132 | { CreateDateRole, "createDate" },
133 | { RatingCountRole, "ratingCount" },
134 | { RatingRole, "rating" },
135 | { TitleRole, "title" },
136 | { UserNameRole, "userName" },
137 | { IconSourceRole, "iconSource" },
138 | { SinceUpdateRole, "sinceUpdate" },
139 | { CategoryRole, "category" },
140 | { CategoryIdRole, "categoryId" },
141 | { VisibilityRole, "visible" }
142 | };
143 | }
144 |
145 | void OrnAppsModel::fetchMore(const QModelIndex &parent)
146 | {
147 | if (parent.isValid() || mResource.isEmpty())
148 | {
149 | return;
150 | }
151 | OrnAbstractListModel::fetch(mResource);
152 | }
153 |
--------------------------------------------------------------------------------
/src/ornappsmodel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ornabstractlistmodel.h"
4 | #include "ornapplistitem.h"
5 |
6 | class OrnAppsModel : public OrnAbstractListModel
7 | {
8 | Q_OBJECT
9 | Q_PROPERTY(bool fetchable READ fetchable WRITE setFetchable NOTIFY fetchableChanged)
10 | Q_PROPERTY(QString resource READ resource WRITE setResource NOTIFY resourceChanged)
11 |
12 | public:
13 |
14 | enum Role
15 | {
16 | SortRole = Qt::UserRole,
17 | ValidityRole,
18 | BookmarkRole,
19 | PackageStatusRole,
20 | AppIdRole,
21 | CreateDateRole,
22 | RatingCountRole,
23 | RatingRole,
24 | TitleRole,
25 | UserNameRole,
26 | IconSourceRole,
27 | SinceUpdateRole,
28 | CategoryRole,
29 | CategoryIdRole,
30 | VisibilityRole,
31 | };
32 | Q_ENUM(Role)
33 |
34 | OrnAppsModel(bool fetchable = true, QObject *parent = nullptr);
35 |
36 | bool fetchable() const
37 | { return mFetchable; }
38 |
39 | void setFetchable(bool fetchable);
40 |
41 | QString resource() const
42 | { return mResource; }
43 |
44 | void setResource(const QString &resource);
45 |
46 | signals:
47 | void fetchableChanged();
48 | void resourceChanged();
49 |
50 | private:
51 | QString mResource;
52 |
53 | // QAbstractItemModel interface
54 | public:
55 | QVariant data(const QModelIndex &index, int role) const override;
56 | QHash roleNames() const override;
57 | void fetchMore(const QModelIndex &parent) override;
58 | };
59 |
--------------------------------------------------------------------------------
/src/ornbackup.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | class OrnBackup : public QObject
8 | {
9 | Q_OBJECT
10 | Q_PROPERTY(Status status READ status NOTIFY statusChanged)
11 |
12 | public:
13 |
14 | enum BackupItem
15 | {
16 | BackupRepos = 0x001,
17 | BackupInstalled = 0x002,
18 | BackupBookmarks = 0x004,
19 | };
20 | Q_ENUM(BackupItem)
21 | Q_DECLARE_FLAGS(BackupItems, BackupItem)
22 | Q_FLAGS(BackupItems)
23 |
24 | enum Status
25 | {
26 | Idle,
27 | BackingUp,
28 | RestoringBookmarks,
29 | RestoringRepos,
30 | RefreshingRepos,
31 | SearchingPackages,
32 | InstallingPackages,
33 | };
34 | Q_ENUM(Status)
35 |
36 | enum Error
37 | {
38 | NoError,
39 | DirectoryError,
40 | };
41 | Q_ENUM(Error)
42 |
43 | explicit OrnBackup(QObject *parent = nullptr);
44 |
45 | Status status() const;
46 |
47 | Q_INVOKABLE static QVariantMap details(const QString &path);
48 | Q_INVOKABLE void backup(const QString &filePath, OrnBackup::BackupItems items);
49 | Q_INVOKABLE void restore(const QString &filePath);
50 | Q_INVOKABLE QStringList notFound() const;
51 |
52 | signals:
53 | void statusChanged();
54 | void backupError(OrnBackup::Error err);
55 | void backedUp();
56 | void restored();
57 |
58 | private slots:
59 | void pSearchPackages();
60 | void pAddPackage(quint32 info, const QString &packageId, const QString &summary);
61 | void pInstallPackages();
62 | void pFinishRestore();
63 |
64 | private:
65 | void setStatus(OrnBackup::Status status);
66 | void pBackup(const QString &filePath, BackupItems items);
67 | void pRestore(const QString &filePath);
68 | void pRefreshRepos();
69 |
70 | private:
71 | Status mStatus{Idle};
72 | QStringList mNamesToSearch;
73 | // Name, version
74 | QHash mInstalled;
75 | QMultiHash mPackagesToInstall;
76 | };
77 |
--------------------------------------------------------------------------------
/src/ornbookmarksmodel.cpp:
--------------------------------------------------------------------------------
1 | #include "ornbookmarksmodel.h"
2 | #include "ornapplistitem.h"
3 | #include "ornclient.h"
4 | #include "ornconst.h"
5 |
6 | #include
7 | #include
8 |
9 | QString compactResource(quint32 appid)
10 | {
11 | return QStringLiteral("apps/%0/compact").arg(appid);
12 | }
13 |
14 | OrnBookmarksModel::OrnBookmarksModel(QObject *parent)
15 | : OrnAppsModel(false, parent)
16 | {
17 | auto client = OrnClient::instance();
18 | connect(client, &OrnClient::bookmarkChanged, this,
19 | [this, client](quint32 appId, bool bookmarked)
20 | {
21 | if (this->canFetchMore(QModelIndex()))
22 | {
23 | // Model was not initialized yet so just ignore the signal
24 | return;
25 | }
26 |
27 | if (bookmarked)
28 | {
29 | auto request = client->apiRequest(compactResource(appId));
30 | qDebug() << "Fetching data from" << request.url().toString();
31 | auto reply = client->networkAccessManager()->get(request);
32 | connect(reply, &QNetworkReply::finished, [this, client, reply, appId]()
33 | {
34 | auto doc = client->processReply(reply);
35 | if (doc.isObject())
36 | {
37 | qDebug() << "Adding app" << appId << "to bookmarks model";
38 | QJsonArray arr({doc.object()});
39 | this->processReply(QJsonDocument(arr));
40 | }
41 | });
42 | }
43 | else
44 | {
45 | for (size_t i = 0, s = mData.size(); i < s; ++i)
46 | {
47 | if (mData[i].appId == appId)
48 | {
49 | qDebug() << "Removing app" << appId << "from bookmarks model";
50 | this->beginRemoveRows(QModelIndex(), i, i);
51 | mData.erase(mData.begin() + i);
52 | mData.shrink_to_fit();
53 | this->endRemoveRows();
54 | return;
55 | }
56 | }
57 | }
58 | });
59 | }
60 |
61 | void OrnBookmarksModel::fetchMore(const QModelIndex &parent)
62 | {
63 | if (parent.isValid())
64 | {
65 | return;
66 | }
67 |
68 | auto client = OrnClient::instance();
69 | auto bookmarks = client->bookmarks();
70 | auto size = bookmarks.size();
71 |
72 | if (size == 0)
73 | {
74 | return;
75 | }
76 |
77 | mFetching = true;
78 | emit this->fetchingChanged();
79 |
80 | for (const auto &appid : bookmarks)
81 | {
82 | auto request = client->apiRequest(compactResource(appid));
83 | qDebug() << "Fetching data from" << request.url().toString();
84 | auto reply = client->networkAccessManager()->get(request);
85 | connect(reply, &QNetworkReply::finished, this,
86 | [this, client, size, reply, appid]()
87 | {
88 | auto doc = client->processReply(reply);
89 | if (doc.isObject())
90 | {
91 | mFetchedApps.append(doc.object());
92 | }
93 | else
94 | {
95 | QJsonObject badApp;
96 | badApp.insert(OrnConst::appid, QString::number(appid));
97 | mFetchedApps.append(badApp);
98 | }
99 | if (mFetchedApps.size() == size)
100 | {
101 | this->processReply(QJsonDocument(mFetchedApps));
102 | mFetchedApps = QJsonArray();
103 | mFetching = false;
104 | emit this->fetchingChanged();
105 | }
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/ornbookmarksmodel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ornappsmodel.h"
4 |
5 | class OrnBookmarksModel : public OrnAppsModel
6 | {
7 | Q_OBJECT
8 |
9 | public:
10 | explicit OrnBookmarksModel(QObject *parent = nullptr);
11 |
12 | private:
13 | QJsonArray mFetchedApps;
14 |
15 | // QAbstractItemModel interface
16 | public:
17 | void fetchMore(const QModelIndex &parent) override;
18 | };
19 |
--------------------------------------------------------------------------------
/src/orncategoriesmodel.cpp:
--------------------------------------------------------------------------------
1 | #include "orncategoriesmodel.h"
2 | #include "ornclient.h"
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 |
12 | OrnCategoriesModel::OrnCategoriesModel(QObject *parent)
13 | : OrnAbstractListModel(false, parent)
14 | {
15 | connect(OrnClient::instance(), &OrnClient::categoryVisibilityChanged,
16 | this, [this](quint32 categoryId, bool visible)
17 | {
18 | Q_UNUSED(visible)
19 |
20 | for (size_t i = 0, size = mData.size(); i < size; ++i)
21 | {
22 | if (mData[i].categoryId == categoryId)
23 | {
24 | auto ind = this->createIndex(i, 0);
25 | emit this->dataChanged(ind, ind, {VisibilityRole});
26 | return;
27 | }
28 | }
29 | });
30 | }
31 |
32 | QVariantList childCategories(const std::deque &data, quint32 parentID)
33 | {
34 | QVariantList res;
35 | for (const auto &cat : data)
36 | {
37 | if (cat.parents.contains(parentID)) {
38 | res.append(cat.categoryId);
39 | }
40 | }
41 | return res;
42 | }
43 |
44 | QVariant OrnCategoriesModel::data(const QModelIndex &index, int role) const
45 | {
46 | if (!index.isValid())
47 | {
48 | return QVariant();
49 | }
50 |
51 | const auto &category = mData[index.row()];
52 | switch (role) {
53 | case CategoryIdRole:
54 | return category.categoryId;
55 | case AppsCountRole:
56 | return category.appsCount;
57 | case DepthRole:
58 | return category.depth;
59 | case NameRole:
60 | return category.name;
61 | case VisibilityRole:
62 | return OrnClient::instance()->categoryVisible(category.categoryId);
63 | case ChildrenRole:
64 | return childCategories(mData, category.categoryId);
65 | default:
66 | return QVariant();
67 | }
68 | }
69 |
70 | void OrnCategoriesModel::fetchMore(const QModelIndex &parent)
71 | {
72 | if (parent.isValid())
73 | {
74 | return;
75 | }
76 | OrnAbstractListModel::fetch(QStringLiteral("categories"));
77 | }
78 |
79 | QHash OrnCategoriesModel::roleNames() const
80 | {
81 | return {
82 | { CategoryIdRole, "categoryId" },
83 | { AppsCountRole, "appsCount" },
84 | { DepthRole, "depth" },
85 | { NameRole, "name" },
86 | { VisibilityRole, "visible" },
87 | { ChildrenRole, "children" }
88 | };
89 | }
90 |
91 | void OrnCategoriesModel::processReply(const QJsonDocument &jsonDoc)
92 | {
93 | auto categoriesArray = jsonDoc.array();
94 | if (categoriesArray.isEmpty())
95 | {
96 | qWarning() << "Api reply is empty";
97 | return;
98 | }
99 |
100 | QString childrenKey{QStringLiteral("childrens")};
101 | std::function compare =
102 | [](const OrnCategoryListItem &a, const OrnCategoryListItem &b) -> bool
103 | {
104 | return a.name < b.name;
105 | };
106 | std::function&, const QJsonObject&)> parse;
107 | parse = [&childrenKey, &compare, &parse](std::deque &data, const QJsonObject &jsonObject)
108 | {
109 | data.emplace_back(jsonObject);
110 | if (jsonObject.contains(childrenKey))
111 | {
112 | auto childrenArray = jsonObject[childrenKey].toArray();
113 | for (const QJsonValueRef child : childrenArray)
114 | {
115 | parse(data, child.toObject());
116 | }
117 | auto end = data.end();
118 | std::sort(end - childrenArray.size(), end, compare);
119 | }
120 | };
121 |
122 | for (const QJsonValueRef category : categoriesArray)
123 | {
124 | parse(mData, category.toObject());
125 | }
126 |
127 | auto size = mData.size();
128 | this->beginInsertRows(QModelIndex(), 0, size - 1);
129 | this->endInsertRows();
130 | qDebug() << size << "items have been added to the model";
131 | mFetching = false;
132 | mCanFetchMore = false;
133 | emit this->fetchingChanged();
134 | }
135 |
--------------------------------------------------------------------------------
/src/orncategoriesmodel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ornabstractlistmodel.h"
4 | #include "orncategorylistitem.h"
5 |
6 | /**
7 | * @brief The categories model class
8 | * This will work properly only if api response contains sorted categories
9 | */
10 | class OrnCategoriesModel : public OrnAbstractListModel
11 | {
12 | Q_OBJECT
13 | public:
14 | enum Role
15 | {
16 | CategoryIdRole = Qt::UserRole,
17 | AppsCountRole,
18 | DepthRole,
19 | NameRole,
20 | VisibilityRole,
21 | ChildrenRole,
22 | };
23 | Q_ENUM(Role)
24 |
25 | explicit OrnCategoriesModel(QObject *parent = nullptr);
26 |
27 | // QAbstractItemModel interface
28 | public:
29 | QVariant data(const QModelIndex &index, int role) const override;
30 | void fetchMore(const QModelIndex &parent) override;
31 | QHash roleNames() const override;
32 |
33 | // OrnAbstractListModel interface
34 | protected:
35 | void processReply(const QJsonDocument &jsonDoc) override;
36 | };
37 |
--------------------------------------------------------------------------------
/src/orncategorylistitem.cpp:
--------------------------------------------------------------------------------
1 | #include "orncategorylistitem.h"
2 | #include "ornutils.h"
3 | #include "ornconst.h"
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 |
11 | using categories_t = const QMap;
12 |
13 | Q_GLOBAL_STATIC_WITH_ARGS(categories_t, categories, ({
14 | //% "Coding Competition"
15 | { 3092, QT_TRID_NOOP("orn-cat-coding-competition") },
16 | //% "Applications"
17 | { 1, QT_TRID_NOOP("orn-cat-applications") },
18 | //% "Application"
19 | { 257, QT_TRID_NOOP("orn-cat-application") },
20 | //% "Adult Content"
21 | { 4206, QT_TRID_NOOP("orn-cat-adult-content") },
22 | //% "Ambience & Themes"
23 | { 1845, QT_TRID_NOOP("orn-cat-ambience-themes") },
24 | //% "Business"
25 | { 2, QT_TRID_NOOP("orn-cat-business") },
26 | //% "City guides & maps"
27 | { 3, QT_TRID_NOOP("orn-cat-city-guides-maps") },
28 | //% "Education & Science"
29 | { 1324, QT_TRID_NOOP("orn-cat-education-science") },
30 | //% "Entertainment"
31 | { 4, QT_TRID_NOOP("orn-cat-entertainment") },
32 | //% "Music"
33 | { 5, QT_TRID_NOOP("orn-cat-music") },
34 | //% "Network"
35 | { 8, QT_TRID_NOOP("orn-cat-network") },
36 | //% "News & info"
37 | { 6, QT_TRID_NOOP("orn-cat-news-info") },
38 | //% "Patches"
39 | { 2983, QT_TRID_NOOP("orn-cat-patches") },
40 | //% "Photo & video"
41 | { 7, QT_TRID_NOOP("orn-cat-photo-video") },
42 | //% "Public Transport"
43 | { 3755, QT_TRID_NOOP("orn-cat-public-transport") },
44 | //% "Social Networks"
45 | { 9, QT_TRID_NOOP("orn-cat-social-networks") },
46 | //% "Sports"
47 | { 10, QT_TRID_NOOP("orn-cat-sports") },
48 | //% "System"
49 | { 147, QT_TRID_NOOP("orn-cat-system") },
50 | //% "Unknown"
51 | { 250, QT_TRID_NOOP("orn-cat-unknown") },
52 | //% "Utilities"
53 | { 11, QT_TRID_NOOP("orn-cat-utilities") },
54 | //% "Games"
55 | { 12, QT_TRID_NOOP("orn-cat-games") },
56 | //% "Game"
57 | { 256, QT_TRID_NOOP("orn-cat-game") },
58 | //% "Action"
59 | { 13, QT_TRID_NOOP("orn-cat-action") },
60 | //% "Adventure"
61 | { 14, QT_TRID_NOOP("orn-cat-adventure") },
62 | //% "Arcade"
63 | { 15, QT_TRID_NOOP("orn-cat-arcade") },
64 | //% "Card & casino"
65 | { 16, QT_TRID_NOOP("orn-cat-card-casino") },
66 | //% "Education"
67 | { 17, QT_TRID_NOOP("orn-cat-education") },
68 | //% "Puzzle"
69 | { 18, QT_TRID_NOOP("orn-cat-puzzle") },
70 | //% "Sports"
71 | { 19, QT_TRID_NOOP("orn-cat-sports") },
72 | //% "Strategy"
73 | { 20, QT_TRID_NOOP("orn-cat-strategy") },
74 | //% "Trivia"
75 | { 21, QT_TRID_NOOP("orn-cat-trivia") },
76 | //% "Translations"
77 | { 3413, QT_TRID_NOOP("orn-cat-translations") },
78 | //% "Fonts"
79 | { 3155, QT_TRID_NOOP("orn-cat-fonts") },
80 | //% "Libraries"
81 | { 247, QT_TRID_NOOP("orn-cat-libraries") },
82 | }));
83 |
84 | static const QString KEY_DEPTH {QStringLiteral("depth")};
85 | static const QString KEY_PARENTS {QStringLiteral("parents")};
86 |
87 | OrnCategoryListItem::OrnCategoryListItem(const QJsonObject &data)
88 | : categoryId(OrnUtils::toUint(data[OrnConst::tid]))
89 | , appsCount(OrnUtils::toUint(data[OrnConst::appsCount]))
90 | , depth(data[KEY_DEPTH].toVariant().toUInt())
91 | , name(categoryName(categoryId))
92 | {
93 | auto array = data[KEY_PARENTS].toArray();
94 | for (const QJsonValueRef v : array)
95 | {
96 | parents << OrnUtils::toUint(v);
97 | }
98 | }
99 |
100 | QString OrnCategoryListItem::categoryName(quint32 tid)
101 | {
102 | if (categories->contains(tid))
103 | {
104 | return qtTrId((*categories)[tid]);
105 | }
106 | qWarning() << "Categories dictionary does not contain tid"
107 | << tid << "- dictionary update can be required";
108 | //% "Unknown category"
109 | return qtTrId("orn-cat-unknown2");
110 | }
111 |
--------------------------------------------------------------------------------
/src/orncategorylistitem.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | class QJsonObject;
7 |
8 | struct OrnCategoryListItem
9 | {
10 | friend class OrnCategoriesModel;
11 |
12 | OrnCategoryListItem(const QJsonObject &data);
13 |
14 | static QString categoryName(quint32 tid);
15 |
16 | quint32 categoryId;
17 | quint32 appsCount;
18 | quint32 depth;
19 | QString name;
20 | QList parents;
21 | };
22 |
--------------------------------------------------------------------------------
/src/ornclient.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | class QQmlEngine;
7 | class QJSEngine;
8 | class QNetworkAccessManager;
9 | class QNetworkRequest;
10 | class QNetworkReply;
11 |
12 | class OrnClientPrivate;
13 |
14 | class OrnClient : public QObject
15 | {
16 | friend class OrnBackup;
17 |
18 | Q_OBJECT
19 | Q_PROPERTY(bool authorised READ authorised NOTIFY authorisedChanged)
20 | Q_PROPERTY(bool cookieIsValid READ cookieIsValid NOTIFY cookieIsValidChanged)
21 | Q_PROPERTY(bool isPublisher READ isPublisher NOTIFY authorisedChanged)
22 | Q_PROPERTY(quint32 userId READ userId NOTIFY authorisedChanged)
23 | Q_PROPERTY(QString userName READ userName NOTIFY authorisedChanged)
24 | Q_PROPERTY(QString userIconSource READ userIconSource NOTIFY authorisedChanged)
25 |
26 | public:
27 |
28 | enum Error
29 | {
30 | NetworkError,
31 | AuthorisationError,
32 | CommentSendError,
33 | CommentDeleteError,
34 | };
35 | Q_ENUM(Error)
36 |
37 | enum CommentAction
38 | {
39 | CommentAdded,
40 | CommentEdited,
41 | CommentDeleted,
42 | };
43 | Q_ENUM(CommentAction)
44 |
45 | static OrnClient *instance();
46 | static inline QObject *qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
47 | {
48 | Q_UNUSED(engine)
49 | Q_UNUSED(scriptEngine)
50 |
51 | return OrnClient::instance();
52 | }
53 |
54 | QNetworkRequest apiRequest(const QString &resource, const QUrlQuery &query = QUrlQuery()) const;
55 | QNetworkAccessManager *networkAccessManager() const;
56 | QJsonDocument processReply(QNetworkReply *reply, Error code = NetworkError);
57 |
58 | bool authorised() const;
59 | bool cookieIsValid() const;
60 | bool isPublisher() const;
61 | quint32 userId() const;
62 | QString userName() const;
63 | QString userIconSource();
64 |
65 | Q_INVOKABLE QList bookmarks() const;
66 | Q_INVOKABLE bool hasBookmark(quint32 appId) const;
67 | Q_INVOKABLE bool addBookmark(quint32 appId);
68 | Q_INVOKABLE bool removeBookmark(quint32 appId);
69 |
70 | Q_INVOKABLE bool categoryVisible(quint32 categoryId) const;
71 | Q_INVOKABLE void setCategoryVisibility(quint32 categoryId, bool visible);
72 | Q_INVOKABLE void toggleCategoryVisibility(quint32 categoryId);
73 |
74 | public slots:
75 | void login(const QString &username, QString password, bool savePassword = false);
76 | void logout();
77 |
78 | void comment(quint32 appId, const QString &body, quint32 parentId = 0);
79 | void editComment(quint32 appId, quint32 commentId, const QString &body);
80 | void deleteComment(quint32 appId, quint32 commentId);
81 |
82 | void vote(quint32 appId, quint32 value);
83 |
84 | signals:
85 | void error(OrnClient::Error code);
86 | void authorisedChanged();
87 | void dayToExpiry();
88 | void cookieIsValidChanged();
89 | void commentActionFinished(OrnClient::CommentAction action, quint32 appId, quint32 cid);
90 | void bookmarkChanged(quint32 appid, bool bookmarked);
91 | void categoryVisibilityChanged(quint32 categoryId, bool visible);
92 | void userVoteFinished(quint32 appId, quint32 userVote, quint32 count, float rating);
93 |
94 | private:
95 | explicit OrnClient(QObject *parent = nullptr);
96 |
97 | Q_DECLARE_PRIVATE(OrnClient)
98 | };
99 |
--------------------------------------------------------------------------------
/src/ornclient_p.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ornsecrets.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | class QSettings;
11 | class QTimer;
12 | class QNetworkAccessManager;
13 | class OrnClient;
14 |
15 | class OrnClientPrivate : public QObjectPrivate
16 | {
17 | Q_DISABLE_COPY(OrnClientPrivate)
18 | Q_DECLARE_PUBLIC(OrnClient)
19 |
20 | public:
21 | OrnClientPrivate() = default;
22 | ~OrnClientPrivate() override;
23 |
24 | void removeUser();
25 | bool relogin();
26 | void setCookieTimer();
27 | QVariant userProperty(const QString &key) const;
28 |
29 | QSettings *settings{nullptr};
30 | QTimer *cookieTimer{nullptr};
31 | QNetworkAccessManager *nam{nullptr};
32 | QSet bookmarks;
33 | QSet hiddenCategories;
34 | QByteArray lang;
35 | QByteArray userToken;
36 | QNetworkCookie userCookie;
37 | OrnSecrets secrets;
38 |
39 | static void prepareComment(QJsonObject &object, const QString &body);
40 | };
41 |
--------------------------------------------------------------------------------
/src/orncommentlistitem.cpp:
--------------------------------------------------------------------------------
1 | #include "orncommentlistitem.h"
2 | #include "ornutils.h"
3 | #include "ornconst.h"
4 |
5 | #include
6 |
7 |
8 | OrnCommentListItem::OrnCommentListItem(const QJsonObject &data)
9 | : commentId(OrnUtils::toUint(data[OrnConst::cid]))
10 | , parentId(OrnUtils::toUint(data[OrnConst::pid]))
11 | , created(OrnUtils::toUint(data[OrnConst::created]))
12 | , text(OrnUtils::toString(data[OrnConst::text]))
13 | {
14 | auto user = data[OrnConst::user].toObject();
15 | userId = OrnUtils::toUint(user[OrnConst::uid]);
16 | userName = OrnUtils::toString(user[OrnConst::name]);
17 | userIconSource = OrnUtils::toString(user[OrnConst::picture].toObject()[OrnConst::url]);
18 | }
19 |
--------------------------------------------------------------------------------
/src/orncommentlistitem.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | class QJsonObject;
6 |
7 | struct OrnCommentListItem
8 | {
9 | OrnCommentListItem(const QJsonObject &data);
10 |
11 | quint32 commentId;
12 | quint32 parentId;
13 | quint32 created;
14 | quint32 userId;
15 | QString userName;
16 | QString userIconSource;
17 | QString text;
18 | };
19 |
--------------------------------------------------------------------------------
/src/orncommentsmodel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ornabstractlistmodel.h"
4 | #include "orncommentlistitem.h"
5 |
6 | struct OrnCommentListItem;
7 | class QNetworkReply;
8 |
9 | class OrnCommentsModel : public OrnAbstractListModel
10 | {
11 | Q_OBJECT
12 | Q_PROPERTY(quint32 appId READ appId WRITE setAppId NOTIFY appIdChanged)
13 |
14 | public:
15 | enum Role
16 | {
17 | CommentIdRole = Qt::UserRole,
18 | ParentIdRole,
19 | CreatedRole,
20 | UserIdRole,
21 | UserNameRole,
22 | ParentUserNameRole,
23 | UserIconSourceRole,
24 | TextRole
25 | };
26 | Q_ENUM(Role)
27 |
28 | explicit OrnCommentsModel(QObject *parent = nullptr);
29 |
30 | quint32 appId() const;
31 | void setAppId(quint32 appId);
32 |
33 | Q_INVOKABLE int findItemRow(quint32 cid) const;
34 |
35 | signals:
36 | void appIdChanged();
37 |
38 | private:
39 | QNetworkReply *fetchComment(quint32 cid);
40 | QJsonObject processReply(QNetworkReply *reply);
41 |
42 | private:
43 | quint32 mAppId{0};
44 |
45 | // QAbstractItemModel interface
46 | public:
47 | QVariant data(const QModelIndex &index, int role) const override;
48 | void fetchMore(const QModelIndex &parent) override;
49 | QHash roleNames() const override;
50 | };
51 |
--------------------------------------------------------------------------------
/src/ornconst.cpp:
--------------------------------------------------------------------------------
1 | #include "ornconst.h"
2 |
3 | const QString OrnConst::appid {QStringLiteral("appid")};
4 | const QString OrnConst::appsCount {QStringLiteral("apps_count")};
5 | const QString OrnConst::body {QStringLiteral("body")};
6 | const QString OrnConst::bookmarks {QStringLiteral("bookmarks")};
7 | const QString OrnConst::category {QStringLiteral("category")};
8 | const QString OrnConst::changelog {QStringLiteral("changelog")};
9 | const QString OrnConst::cid {QStringLiteral("cid")};
10 | const QString OrnConst::comments {QStringLiteral("comments")};
11 | const QString OrnConst::commentsCount {QStringLiteral("comments_count")};
12 | const QString OrnConst::commentsOpen {QStringLiteral("comments_open")};
13 | const QString OrnConst::count {QStringLiteral("count")};
14 | const QString OrnConst::created {QStringLiteral("created")};
15 | const QString OrnConst::downloads {QStringLiteral("downloads")};
16 | const QString OrnConst::icon {QStringLiteral("icon")};
17 | const QString OrnConst::id {QStringLiteral("id")};
18 | const QString OrnConst::installed {QStringLiteral("installed")};
19 | const QString OrnConst::large {QStringLiteral("large")};
20 | const QString OrnConst::mail {QStringLiteral("mail")};
21 | const QString OrnConst::name {QStringLiteral("name")};
22 | const QString OrnConst::package {QStringLiteral("package")};
23 | const QString OrnConst::picture {QStringLiteral("picture")};
24 | const QString OrnConst::pid {QStringLiteral("pid")};
25 | const QString OrnConst::publisher {QStringLiteral("publisher")};
26 | const QString OrnConst::rating {QStringLiteral("rating")};
27 | const QString OrnConst::realname {QStringLiteral("realname")};
28 | const QString OrnConst::roles {QStringLiteral("roles")};
29 | const QString OrnConst::screenshots {QStringLiteral("screenshots")};
30 | const QString OrnConst::tags {QStringLiteral("tags")};
31 | const QString OrnConst::text {QStringLiteral("text")};
32 | const QString OrnConst::thumb {QStringLiteral("thumb")};
33 | const QString OrnConst::thumbs {QStringLiteral("thumbs")};
34 | const QString OrnConst::tid {QStringLiteral("tid")};
35 | const QString OrnConst::title {QStringLiteral("title")};
36 | const QString OrnConst::uid {QStringLiteral("uid")};
37 | const QString OrnConst::und {QStringLiteral("und")};
38 | const QString OrnConst::updated {QStringLiteral("updated")};
39 | const QString OrnConst::url {QStringLiteral("url")};
40 | const QString OrnConst::user {QStringLiteral("user")};
41 | const QString OrnConst::userVote {QStringLiteral("user_vote")};
42 | const QString OrnConst::value {QStringLiteral("value")};
43 |
--------------------------------------------------------------------------------
/src/ornconst.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | struct OrnConst {
6 | static const QString appid;
7 | static const QString appsCount;
8 | static const QString body;
9 | static const QString bookmarks;
10 | static const QString category;
11 | static const QString changelog;
12 | static const QString cid;
13 | static const QString comments;
14 | static const QString commentsCount;
15 | static const QString commentsOpen;
16 | static const QString count;
17 | static const QString created;
18 | static const QString downloads;
19 | static const QString icon;
20 | static const QString id;
21 | static const QString installed;
22 | static const QString large;
23 | static const QString mail;
24 | static const QString name;
25 | static const QString package;
26 | static const QString picture;
27 | static const QString pid;
28 | static const QString publisher;
29 | static const QString rating;
30 | static const QString realname;
31 | static const QString roles;
32 | static const QString screenshots;
33 | static const QString tags;
34 | static const QString text;
35 | static const QString thumb;
36 | static const QString thumbs;
37 | static const QString tid;
38 | static const QString title;
39 | static const QString uid;
40 | static const QString und;
41 | static const QString updated;
42 | static const QString url;
43 | static const QString user;
44 | static const QString userVote;
45 | static const QString value;
46 | };
47 |
--------------------------------------------------------------------------------
/src/orninstalledappsmodel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "orninstalledpackage.h"
6 |
7 | class OrnInstalledAppsModel : public QAbstractListModel
8 | {
9 | Q_OBJECT
10 |
11 | public:
12 |
13 | enum Roles
14 | {
15 | NameRole = Qt::UserRole + 1,
16 | TitleRole,
17 | TitleUnlocalizedRole,
18 | VersionRole,
19 | IconRole,
20 | SortRole,
21 | SectionRole,
22 | UpdateAvailableRole,
23 | UpdateVersionRole,
24 | IdRole
25 | };
26 | Q_ENUM(Roles)
27 |
28 | explicit OrnInstalledAppsModel(QObject *parent = nullptr);
29 |
30 | public slots:
31 | void reset();
32 |
33 | private slots:
34 | void onInstalledPackages(const OrnInstalledPackageList &packages);
35 | void onPackageInstalled(const QString &packageName);
36 | void onPackageRemoved(const QString &packageName);
37 | void onUpdatablePackagesChanged();
38 |
39 | private:
40 | bool mResetting{false};
41 | OrnInstalledPackageList mData;
42 |
43 | // QAbstractItemModel interface
44 | public:
45 | int rowCount(const QModelIndex &parent) const override;
46 | QVariant data(const QModelIndex &index, int role) const override;
47 | QHash roleNames() const override;
48 | };
49 |
--------------------------------------------------------------------------------
/src/orninstalledpackage.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | struct OrnInstalledPackage
7 | {
8 | bool updateAvailable{false};
9 | QString id;
10 | QString name;
11 | QString title;
12 | QString titleUnlocalized;
13 | QString icon;
14 | };
15 |
16 | using OrnInstalledPackageList = QList;
17 |
18 | Q_DECLARE_METATYPE(QList)
19 |
--------------------------------------------------------------------------------
/src/ornpackageversion.cpp:
--------------------------------------------------------------------------------
1 | #include "ornpackageversion.h"
2 |
3 | #include
4 |
5 |
6 | QVariantList splitVersion(const QString &version)
7 | {
8 | QVariantList parts;
9 | static QRegularExpression sep_re(QStringLiteral("[.+~-]"));
10 | bool ok;
11 | const auto sparts = version.split(sep_re);
12 | for (const auto &s : sparts)
13 | {
14 | auto v = s.toInt(&ok);
15 | parts << (ok ? QVariant(v) : QVariant(s));
16 | }
17 | return parts;
18 | }
19 |
20 | OrnPackageVersion::OrnPackageVersion(const QString &version)
21 | : version(version)
22 | , versionParts(splitVersion(version))
23 | {}
24 |
25 | OrnPackageVersion::OrnPackageVersion(quint64 dsize, quint64 isize,
26 | const QString &version, QString arch, QString alias)
27 | : downloadSize(dsize)
28 | , installSize(isize)
29 | , version(version)
30 | , arch(std::move(arch))
31 | , repoAlias(std::move(alias))
32 | , versionParts(splitVersion(version))
33 | {}
34 |
35 | void OrnPackageVersion::clear()
36 | {
37 | downloadSize = 0;
38 | installSize = 0;
39 | version.clear();
40 | arch.clear();
41 | repoAlias.clear();
42 | versionParts.clear();
43 | }
44 |
45 | QString OrnPackageVersion::packageId(const QString &name) const
46 | {
47 | QString id(name);
48 | QChar sep(';');
49 | id.reserve(name.size() + version.size() + arch.size() + repoAlias.size() + 3);
50 | id.append(sep).append(version).append(sep).append(arch).append(sep).append(repoAlias);
51 | return id;
52 | }
53 |
54 | bool OrnPackageVersion::operator ==(const OrnPackageVersion &other) const
55 | {
56 | return downloadSize == other.downloadSize &&
57 | installSize == other.installSize &&
58 | version == other.version &&
59 | arch == other.arch &&
60 | repoAlias == other.repoAlias;
61 | }
62 |
63 | bool OrnPackageVersion::operator <(const OrnPackageVersion &other) const
64 | {
65 | if (versionParts == other.versionParts)
66 | {
67 | return false;
68 | }
69 |
70 | auto leftSize = versionParts.size();
71 | auto rightSize = other.versionParts.size();
72 | auto leftIsShorter = leftSize < rightSize;
73 | auto shorterLength = leftIsShorter ? leftSize : rightSize;
74 |
75 | // Compare the same parts
76 | for (int i = 0; i < shorterLength; ++i)
77 | {
78 | const auto &lp = versionParts[i];
79 | const auto &rp = other.versionParts[i];
80 | if (lp != rp)
81 | {
82 | return lp < rp;
83 | }
84 | }
85 |
86 | // If the same parts are equal then the shorter version is lesser
87 | return leftIsShorter;
88 | }
89 |
--------------------------------------------------------------------------------
/src/ornpackageversion.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | struct OrnPackageVersion
6 | {
7 | quint64 downloadSize{0};
8 | quint64 installSize{0};
9 | QString version;
10 | QString arch;
11 | QString repoAlias;
12 |
13 | OrnPackageVersion() = default;
14 | OrnPackageVersion(const QString &version);
15 | OrnPackageVersion(quint64 dsize, quint64 isize,
16 | const QString &version, QString arch, QString alias);
17 |
18 | void clear();
19 |
20 | QString packageId(const QString &name) const;
21 |
22 | bool operator ==(const OrnPackageVersion &other) const;
23 | inline bool operator !=(const OrnPackageVersion &other) const
24 | { return !this->operator ==(other); }
25 |
26 | bool operator <(const OrnPackageVersion &other) const;
27 |
28 | private:
29 | QVariantList versionParts;
30 | };
31 |
32 | using OrnPackageVersionList = QList;
33 |
34 | Q_DECLARE_METATYPE(QList)
35 |
--------------------------------------------------------------------------------
/src/ornpkdaemon.cpp:
--------------------------------------------------------------------------------
1 | #include "ornpkdaemon.h"
2 | #include "ornpktransaction.h"
3 |
4 | #include
5 |
6 | const QString OrnPkDaemon::serviceName{QStringLiteral("org.freedesktop.PackageKit")};
7 |
8 | OrnPkDaemon::OrnPkDaemon(QObject *parent)
9 | : QDBusAbstractInterface(
10 | serviceName,
11 | QStringLiteral("/org/freedesktop/PackageKit"),
12 | "org.freedesktop.PackageKit",
13 | QDBusConnection::systemBus(),
14 | parent
15 | )
16 | {
17 |
18 | }
19 |
20 | OrnPkTransaction *OrnPkDaemon::transaction() {
21 | auto reply = call(QStringLiteral("CreateTransaction"));
22 | Q_ASSERT_X(reply.type() != QDBusMessage::ErrorMessage, Q_FUNC_INFO,
23 | qPrintable(reply.errorName().append(": ").append(reply.errorMessage())));
24 |
25 | auto t = new OrnPkTransaction(
26 | qvariant_cast(reply.arguments().constFirst()).path(),
27 | false,
28 | parent()
29 | );
30 | Q_ASSERT(t->isValid());
31 |
32 | return t;
33 | }
34 |
--------------------------------------------------------------------------------
/src/ornpkdaemon.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | class OrnPkTransaction;
6 |
7 | class OrnPkDaemon : public QDBusAbstractInterface
8 | {
9 | Q_OBJECT
10 |
11 | public:
12 | static const QString serviceName;
13 |
14 | explicit OrnPkDaemon(QObject *parent = nullptr);
15 |
16 | OrnPkTransaction *transaction();
17 |
18 | signals:
19 | void UpdatesChanged();
20 | void TransactionListChanged(const QStringList &transactions);
21 | };
22 |
--------------------------------------------------------------------------------
/src/ornpktransaction.cpp:
--------------------------------------------------------------------------------
1 | #include "ornpktransaction.h"
2 | #include "ornpkdaemon.h"
3 |
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | constexpr quint64 FlagNone{PackageKit::Transaction::TransactionFlagNone};
10 |
11 | #ifdef QT_DEBUG
12 | QDebug operator<<(QDebug dbg, const OrnPkTransaction *t) {
13 | QDebugStateSaver state{dbg};
14 | dbg.quote().nospace() << "PackageKit::Transaction(" << t->path() << ")";
15 | return dbg;
16 | }
17 | #endif
18 |
19 | OrnPkTransaction::OrnPkTransaction(const QString &path, bool conn, QObject *parent)
20 | : QDBusAbstractInterface(
21 | OrnPkDaemon::serviceName,
22 | path,
23 | "org.freedesktop.PackageKit.Transaction",
24 | QDBusConnection::systemBus(),
25 | parent
26 | )
27 | {
28 | if (conn) {
29 | #ifdef QT_DEBUG
30 | connect(this, &OrnPkTransaction::Finished, this, [this](quint32 exit, quint32 runtime) {
31 | qDebug() << this
32 | << (exit == PackageKit::Transaction::ExitSuccess ? "finished in" : "failed after")
33 | << runtime << "msec";
34 | this->deleteLater();
35 | });
36 | connect(this, &OrnPkTransaction::ErrorCode, this, [this](quint32 code, const QString &details) {
37 | qDebug().noquote().nospace() << this << " error code " << code << ": " << details;
38 | });
39 | #else
40 | connect(this, &OrnPkTransaction::Finished, this, &OrnPkTransaction::deleteLater);
41 | #endif
42 | }
43 | }
44 |
45 | void OrnPkTransaction::resolve(const QStringList &names) {
46 | QString method{QStringLiteral("Resolve")};
47 | qDebug().nospace()
48 | << "Calling " << this << "->" << method.toLatin1().data()
49 | << "(" << FlagNone << ", " << names << ")";
50 | asyncCall(method, FlagNone, names);
51 | }
52 |
53 | void OrnPkTransaction::installPackages(const QStringList &ids) {
54 | QString method{QStringLiteral("InstallPackages")};
55 | qDebug().nospace()
56 | << "Calling " << this << "->" << method.toLatin1().data()
57 | << "(" << FlagNone << ", " << ids << ")";
58 | asyncCall(method, FlagNone, ids);
59 | }
60 |
61 | void OrnPkTransaction::updatePackages(const QStringList &ids) {
62 | QString method{QStringLiteral("UpdatePackages")};
63 | qDebug().nospace()
64 | << "Calling " << this << "->" << method.toLatin1().data()
65 | << "(" << FlagNone << ", " << ids << ")";
66 | asyncCall(method, FlagNone, ids);
67 | }
68 |
69 | void OrnPkTransaction::removePackages(const QStringList &ids, bool autoremove) {
70 | QString method{QStringLiteral("RemovePackages")};
71 | qDebug().nospace()
72 | << "Calling " << this << "->" << method.toLatin1().data()
73 | << "(" << FlagNone << ", " << ids << ", false, " << autoremove << ")";
74 | asyncCall(method, FlagNone, ids, false, autoremove);
75 | }
76 |
77 | void OrnPkTransaction::installFiles(const QStringList &files) {
78 | QString method{QStringLiteral("InstallFiles")};
79 | qDebug().nospace()
80 | << "Calling " << this << "->" << method.toLatin1().data()
81 | << "(" << FlagNone << ", " << files << ")";
82 | asyncCall(method, FlagNone, files);
83 | }
84 |
85 | void OrnPkTransaction::repoRefreshNow(const QString &alias, const QString &force) {
86 | QString method{QStringLiteral("RepoSetData")};
87 | qDebug().nospace().noquote()
88 | << "Calling " << this << "->" << method
89 | << "(\"" << alias << "\", \"refresh-now\", " << force << ")";
90 | asyncCall(method, alias, "refresh-now", force);
91 | }
92 |
93 | void OrnPkTransaction::refreshCache(bool force) {
94 | QString method{QStringLiteral("RefreshCache")};
95 | qDebug().nospace().noquote()
96 | << "Calling " << this << "->" << method << "(" << force << ")";
97 | asyncCall(method, force);
98 | }
99 |
100 | void OrnPkTransaction::getUpdates() {
101 | QString method{QStringLiteral("GetUpdates")};
102 | qDebug().nospace().noquote()
103 | << "Calling " << this << "->" << method << "(" << FlagNone << ")";
104 | asyncCall(method, FlagNone);
105 | }
106 |
--------------------------------------------------------------------------------
/src/ornpktransaction.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ornutils.h"
4 |
5 | #include
6 |
7 | class OrnPkTransaction : public QDBusAbstractInterface
8 | {
9 | Q_OBJECT
10 |
11 | #ifdef QT_DEBUG
12 | friend QDebug operator<<(QDebug dbg, const OrnPkTransaction *t);
13 | #endif
14 |
15 | public:
16 | OrnPkTransaction(const QString &path, bool conn, QObject *parent = nullptr);
17 |
18 | void resolve(const QStringList &names);
19 | void installPackages(const QStringList &ids);
20 | void updatePackages(const QStringList &ids);
21 | void removePackages(const QStringList &ids, bool autoremove = false);
22 |
23 | void installFiles(const QStringList &files);
24 |
25 | void repoRefreshNow(const QString &alias, const QString &force);
26 | void repoRefreshNow(const QString &alias, bool force = false) {
27 | repoRefreshNow(alias, OrnUtils::stringify(force));
28 | }
29 |
30 | void refreshCache(bool force = false);
31 | void getUpdates();
32 |
33 | Q_PROPERTY(uint Role READ role CONSTANT)
34 | uint role() const {
35 | return property("Role").toUInt();
36 | }
37 |
38 | signals:
39 | void ErrorCode(quint32 code, const QString &details);
40 | void Finished(quint32 exit, quint32 runtime);
41 | void Package(quint32 info, const QString &packageId, const QString &summary);
42 | void ItemProgress(const QString &id, uint status, uint percentage);
43 | };
44 |
--------------------------------------------------------------------------------
/src/ornpm_p.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ornpm.h"
4 | #include "orninstalledpackage.h"
5 | #include "ornpkdaemon.h"
6 | #include "ornpktransaction.h"
7 | #include "ornssu.h"
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 |
15 | #define PK_FLAG_NONE quint64(0)
16 |
17 | class OrnPmPrivate : public QObjectPrivate
18 | {
19 | Q_DISABLE_COPY(OrnPmPrivate)
20 | Q_DECLARE_PUBLIC(OrnPm)
21 |
22 | public:
23 | using ornpm_signal_t = void (OrnPm::*)(const QString &);
24 |
25 | OrnPmPrivate() = default;
26 | ~OrnPmPrivate() override = default;
27 |
28 | void initialise();
29 | OrnPkTransaction *transaction();
30 | OrnPkTransaction *currentTransaction();
31 | void preparePackageVersions(const QString &packageName);
32 | bool enableRepos(bool enable);
33 | void removeAllRepos();
34 | void onRepoModified(const QString &alias, OrnPm::RepoAction action);
35 | OrnInstalledPackageList prepareInstalledPackages(const QString &packageName);
36 |
37 | bool startOperation(const QString &name, OrnPm::Operation operation);
38 | void finishOperation(const QString &name);
39 |
40 | void onItemProgress(const QString &id, uint status, uint percentage);
41 |
42 | OrnPkTransaction *packageTransaction(const QString &packageName, OrnPm::Operation operation, OrnPm::PackageStatus status, ornpm_signal_t sgnl);
43 |
44 | // Check for updates
45 | void getUpdates();
46 | // Refresh repos
47 | void refreshNextRepo(quint32 exit, quint32 runtime);
48 |
49 | void processSolvables(bool enabled, std::function callback) const;
50 |
51 | //
52 | using RepoHash = QHash;
53 | using StringSet = QSet;
54 | using StringHash = QHash;
55 | using OperationHash = QHash;
56 |
57 | bool initialised{false};
58 | #ifdef QT_DEBUG
59 | quint64 refreshRuntime{0};
60 | #endif
61 | QString solvPathTmpl;
62 | StringSet archs;
63 | OrnSsu *ssuInterface{nullptr};
64 | OrnPkDaemon *pkDaemon{nullptr};
65 | RepoHash repos;
66 | StringHash installedPackages;
67 | StringHash updatablePackages;
68 | StringHash newUpdatablePackages;
69 | OperationHash operations;
70 | QStringList reposToRefresh;
71 | QString forceRefresh;
72 | };
73 |
--------------------------------------------------------------------------------
/src/ornproxymodel.cpp:
--------------------------------------------------------------------------------
1 | #include "ornproxymodel.h"
2 | #include "ornabstractlistmodel.h"
3 |
4 |
5 | OrnProxyModel::OrnProxyModel(QObject *parent)
6 | : QSortFilterProxyModel(parent)
7 | {
8 | }
9 |
10 | int OrnProxyModel::limit() const
11 | {
12 | return mLimit;
13 | }
14 |
15 | void OrnProxyModel::setLimit(int limit)
16 | {
17 | if (mLimit != limit)
18 | {
19 | mLimit = limit;
20 | emit this->limitChanged();
21 | }
22 | this->invalidate();
23 | }
24 |
25 | void OrnProxyModel::sort(Qt::SortOrder order)
26 | {
27 | QSortFilterProxyModel::sort(0, order);
28 | }
29 |
30 | void OrnProxyModel::reset()
31 | {
32 | static_cast(this->sourceModel())->reset();
33 | }
34 |
35 | int OrnProxyModel::rowCount(const QModelIndex &parent) const
36 | {
37 | auto count = QSortFilterProxyModel::rowCount(parent);
38 | return mLimit > -1 ? std::min(count, mLimit) : count;
39 | }
40 |
41 | bool OrnProxyModel::canFetchMore(const QModelIndex &parent) const
42 | {
43 | auto canFetch = QSortFilterProxyModel::canFetchMore(parent);
44 | return mLimit > -1 ?
45 | QSortFilterProxyModel::rowCount(parent) < mLimit && canFetch :
46 | canFetch;
47 | }
48 |
49 | bool OrnProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
50 | {
51 | auto role = this->filterRole();
52 | // The default value
53 | if (role == Qt::DisplayRole)
54 | {
55 | return true;
56 | }
57 |
58 | auto model = this->sourceModel();
59 | auto idx = model->index(source_row, 0, source_parent);
60 | auto dat = model->data(idx, role);
61 | #ifdef QT_DEBUG
62 | if (dat.type() != QVariant::Bool)
63 | {
64 | qWarning("The filter role must return boolean!");
65 | }
66 | #endif
67 | return dat.toBool();
68 | }
69 |
--------------------------------------------------------------------------------
/src/ornproxymodel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | class OrnProxyModel : public QSortFilterProxyModel
6 | {
7 | Q_OBJECT
8 | Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged)
9 |
10 | public:
11 | explicit OrnProxyModel(QObject *parent = nullptr);
12 |
13 | int limit() const;
14 | void setLimit(int limit);
15 |
16 | signals:
17 | void limitChanged();
18 |
19 | public:
20 | // Why QSortFilterProxyModel has no sort slot?
21 | Q_INVOKABLE void sort(Qt::SortOrder order = Qt::AscendingOrder);
22 |
23 | public slots:
24 | void reset();
25 |
26 | private:
27 | int mLimit{-1};
28 |
29 | // QAbstractItemModel interface
30 | public:
31 | int rowCount(const QModelIndex &parent) const override;
32 | bool canFetchMore(const QModelIndex &parent) const override;
33 |
34 | // QSortFilterProxyModel interface
35 | protected:
36 | bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
37 | };
38 |
--------------------------------------------------------------------------------
/src/ornrepo.cpp:
--------------------------------------------------------------------------------
1 | #include "ornrepo.h"
2 | #include "ornpm.h"
3 |
4 | OrnRepo::OrnRepo(bool enabled, const QString &alias)
5 | : enabled{enabled}
6 | , alias{alias}
7 | , author{alias == OrnPm::storemanRepo ?
8 | //% "Storeman OBS Repository"
9 | qtTrId("orn-storeman-repo-name") :
10 | alias.mid(OrnPm::repoNamePrefix.size())
11 | }
12 | {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/ornrepo.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | struct OrnRepo
6 | {
7 | OrnRepo(bool enabled, const QString &alias);
8 |
9 | bool enabled;
10 | QString alias;
11 | QString author;
12 | };
13 |
14 | using OrnRepoList = QList;
15 |
--------------------------------------------------------------------------------
/src/ornrepomodel.cpp:
--------------------------------------------------------------------------------
1 | #include "ornrepomodel.h"
2 | #include "ornpm.h"
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | OrnRepoModel::OrnRepoModel(QObject *parent)
10 | : QAbstractListModel(parent)
11 | {
12 | auto ornPm = OrnPm::instance();
13 |
14 | connect(ornPm, &OrnPm::initialisedChanged, this, &OrnRepoModel::reset);
15 | connect(ornPm, &OrnPm::enableReposFinished, this, &OrnRepoModel::reset);
16 | connect(ornPm, &OrnPm::removeAllReposFinished, this, &OrnRepoModel::reset);
17 | connect(ornPm, &OrnPm::repoModified, this, &OrnRepoModel::onRepoModified);
18 | connect(this, &OrnRepoModel::modelReset, this, &OrnRepoModel::enabledReposChanged);
19 |
20 | // Delay reset to ensure that modelReset signal is received in qml
21 | QTimer::singleShot(500, this, &OrnRepoModel::reset);
22 | }
23 |
24 | bool OrnRepoModel::hasEnabledRepos() const
25 | {
26 | return mEnabledRepos;
27 | }
28 |
29 | bool OrnRepoModel::hasDisabledRepos() const
30 | {
31 | return mEnabledRepos != mData.size();
32 | }
33 |
34 | void OrnRepoModel::reset()
35 | {
36 | qDebug() << "Resetting model";
37 | this->beginResetModel();
38 |
39 | mData = OrnPm::instance()->repoList();
40 |
41 | const auto enabled = std::count_if(mData.cbegin(), mData.cend(), [](const auto &r) {
42 | return r.enabled;
43 | });
44 |
45 | if (mEnabledRepos != enabled)
46 | {
47 | mEnabledRepos = enabled;
48 | emit this->enabledReposChanged();
49 | }
50 |
51 | this->endResetModel();
52 | }
53 |
54 | void OrnRepoModel::onRepoModified(const QString &alias, int action)
55 | {
56 | QModelIndex parentIndex;
57 |
58 | if (action == OrnPm::AddRepo)
59 | {
60 | auto row = mData.size();
61 | this->beginInsertRows(parentIndex, row, row);
62 | mData << OrnRepo{true, alias};
63 | ++mEnabledRepos;
64 | emit this->enabledReposChanged();
65 | this->endInsertRows();
66 | return;
67 | }
68 |
69 | int row = 0;
70 | for (auto &repo : mData)
71 | {
72 | if (repo.alias == alias)
73 | {
74 | switch (action)
75 | {
76 | case OrnPm::RemoveRepo:
77 | this->beginRemoveRows(parentIndex, row, row);
78 | mData.removeAt(row);
79 | --mEnabledRepos;
80 | emit this->enabledReposChanged();
81 | this->endRemoveRows();
82 | break;
83 | case OrnPm::DisableRepo:
84 | case OrnPm::EnableRepo:
85 | {
86 | bool enable = action == OrnPm::EnableRepo;
87 | if (repo.enabled != enable)
88 | {
89 | repo.enabled = enable;
90 | auto index = this->createIndex(row, 0);
91 | emit this->dataChanged(index, index, { RepoEnabledRole });
92 | mEnabledRepos += enable ? 1 : -1;
93 | emit this->enabledReposChanged();
94 | }
95 | }
96 | break;
97 | default:
98 | break;
99 | }
100 | return;
101 | }
102 | ++row;
103 | }
104 | }
105 |
106 | int OrnRepoModel::rowCount(const QModelIndex &parent) const
107 | {
108 | return !parent.isValid() ? mData.size() : 0;
109 | }
110 |
111 | QVariant OrnRepoModel::data(const QModelIndex &index, int role) const
112 | {
113 | if (!index.isValid())
114 | {
115 | return QVariant();
116 | }
117 |
118 | auto &repo = mData[index.row()];
119 | switch (role)
120 | {
121 | case RepoAuthorRole:
122 | return repo.author;
123 | case RepoAliasRole:
124 | return repo.alias;
125 | case RepoEnabledRole:
126 | return repo.enabled;
127 | case SortRole:
128 | // Place enabled first and then sort by author
129 | return QString::number(!repo.enabled).append(repo.author);
130 | default:
131 | return QVariant();
132 | }
133 | }
134 |
135 | QHash OrnRepoModel::roleNames() const
136 | {
137 | return {
138 | { RepoAuthorRole, "repoAuthor" },
139 | { RepoAliasRole, "repoAlias" },
140 | { RepoEnabledRole, "repoEnabled" }
141 | };
142 | }
143 |
--------------------------------------------------------------------------------
/src/ornrepomodel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "ornrepo.h"
6 |
7 | class OrnRepoModel : public QAbstractListModel
8 | {
9 | Q_OBJECT
10 | Q_PROPERTY(bool hasEnabledRepos READ hasEnabledRepos NOTIFY enabledReposChanged)
11 | Q_PROPERTY(bool hasDisabledRepos READ hasDisabledRepos NOTIFY enabledReposChanged)
12 |
13 | public:
14 |
15 | enum Roles
16 | {
17 | RepoAuthorRole = Qt::UserRole + 1,
18 | RepoAliasRole,
19 | RepoEnabledRole,
20 | SortRole
21 | };
22 | Q_ENUM(Roles)
23 |
24 | explicit OrnRepoModel(QObject *parent = nullptr);
25 |
26 | bool hasEnabledRepos() const;
27 | bool hasDisabledRepos() const;
28 |
29 | public slots:
30 | void reset();
31 |
32 | signals:
33 | void enabledReposChanged();
34 |
35 | private slots:
36 | void onRepoModified(const QString &alias, int action);
37 |
38 | private:
39 | int mEnabledRepos{0};
40 | OrnRepoList mData;
41 |
42 | // QAbstractItemModel interface
43 | public:
44 | int rowCount(const QModelIndex &parent) const override;
45 | QVariant data(const QModelIndex &index, int role) const override;
46 | QHash roleNames() const override;
47 | };
48 |
--------------------------------------------------------------------------------
/src/ornsearchappsmodel.cpp:
--------------------------------------------------------------------------------
1 | #include "ornsearchappsmodel.h"
2 |
3 | #include
4 |
5 |
6 | OrnSearchAppsModel::OrnSearchAppsModel(QObject *parent)
7 | : OrnAppsModel(true, parent)
8 | {
9 | mCanFetchMore = false;
10 | }
11 |
12 | QString OrnSearchAppsModel::searchKey() const
13 | {
14 | return mSearchKey;
15 | }
16 |
17 | void OrnSearchAppsModel::setSearchKey(const QString &searchKey)
18 | {
19 | if (mSearchKey != searchKey)
20 | {
21 | mSearchKey = searchKey;
22 | emit this->searchKeyChanged();
23 | this->reset();
24 | mCanFetchMore = !mSearchKey.isEmpty();
25 | }
26 | }
27 |
28 | void OrnSearchAppsModel::resetImpl()
29 | {
30 | mPrevReplyHash.clear();
31 | OrnAbstractListModel::resetImpl();
32 | }
33 |
34 | void OrnSearchAppsModel::fetchMore(const QModelIndex &parent)
35 | {
36 | if (parent.isValid())
37 | {
38 | return;
39 | }
40 | if (mSearchKey.isEmpty())
41 | {
42 | qWarning() << "Could not search with an empty search key";
43 | return;
44 | }
45 | QUrlQuery query;
46 | query.addQueryItem(QStringLiteral("keys"), mSearchKey);
47 | OrnAbstractListModel::fetch(QStringLiteral("search/apps"), query);
48 | }
49 |
50 | void OrnSearchAppsModel::processReply(const QJsonDocument &jsonDoc)
51 | {
52 | // An ugly patch for repeating data
53 | auto replyHash = QCryptographicHash::hash(jsonDoc.toJson(), QCryptographicHash::Md5);
54 | if (mPrevReplyHash == replyHash)
55 | {
56 | qDebug() << "Current reply is equal to the previous one. "
57 | "Considering the model has fetched all data";
58 | mCanFetchMore = false;
59 | return;
60 | }
61 | mPrevReplyHash = replyHash;
62 | OrnAbstractListModel::processReply(jsonDoc);
63 | }
64 |
--------------------------------------------------------------------------------
/src/ornsearchappsmodel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ornappsmodel.h"
4 |
5 | class OrnSearchAppsModel : public OrnAppsModel
6 | {
7 | Q_OBJECT
8 | Q_PROPERTY(QString searchKey READ searchKey WRITE setSearchKey NOTIFY searchKeyChanged)
9 |
10 | public:
11 | explicit OrnSearchAppsModel(QObject *parent = nullptr);
12 |
13 | QString searchKey() const;
14 | void setSearchKey(const QString &searchKey);
15 |
16 | signals:
17 | void searchKeyChanged();
18 |
19 | private:
20 | QString mSearchKey;
21 | QByteArray mPrevReplyHash;
22 |
23 | // OrnAbstractListModelBase interface
24 | protected:
25 | void resetImpl() override;
26 |
27 | // QAbstractItemModel interface
28 | public:
29 | void fetchMore(const QModelIndex &parent) override;
30 | void processReply(const QJsonDocument &jsonDoc) override;
31 | };
32 |
--------------------------------------------------------------------------------
/src/ornsecrets.cpp:
--------------------------------------------------------------------------------
1 | #include "ornsecrets_p.h"
2 | #include "ornsecrets.h"
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 |
15 | using namespace Sailfish::Secrets;
16 |
17 |
18 | static const QString collectionName(QStringLiteral("storeman"));
19 |
20 | bool checkResult(const Request &req)
21 | {
22 | auto result = req.result();
23 | auto success = result.errorCode() == Result::NoError;
24 | if (!success) {
25 | qDebug() << result.errorMessage();
26 | }
27 | return success;
28 | }
29 |
30 | Secret::Identifier makeIdent(const QString &name)
31 | {
32 | return Secret::Identifier(name, collectionName, SecretManager::DefaultEncryptedStoragePluginName);
33 | }
34 |
35 | bool createCollection(SecretManager *manager)
36 | {
37 | CreateCollectionRequest ccr;
38 | ccr.setManager(manager);
39 | ccr.setCollectionName(collectionName);
40 | ccr.setAccessControlMode(SecretManager::OwnerOnlyMode);
41 | ccr.setCollectionLockType(CreateCollectionRequest::DeviceLock);
42 | ccr.setDeviceLockUnlockSemantic(SecretManager::DeviceLockKeepUnlocked);
43 | ccr.setStoragePluginName(SecretManager::DefaultEncryptedStoragePluginName);
44 | ccr.setEncryptionPluginName(SecretManager::DefaultEncryptedStoragePluginName);
45 | ccr.startRequest();
46 | ccr.waitForFinished();
47 | return checkResult(ccr);
48 | }
49 |
50 | OrnSecrets::OrnSecrets()
51 | : d_ptr{new OrnSecretsPrivate()}
52 | {
53 | CollectionNamesRequest cnr;
54 | cnr.setManager(d_ptr->secretManager.get());
55 | cnr.setStoragePluginName(SecretManager::DefaultEncryptedStoragePluginName);
56 | cnr.startRequest();
57 | cnr.waitForFinished();
58 | d_ptr->valid = checkResult(cnr) && cnr.collectionNames().contains(collectionName);
59 | }
60 |
61 | OrnSecrets::~OrnSecrets()
62 | {
63 |
64 | }
65 |
66 | bool OrnSecrets::isValid() const
67 | {
68 | return d_ptr->valid;
69 | }
70 |
71 | bool OrnSecrets::storeData(const QString &name, const QByteArray &data)
72 | {
73 | if (!d_ptr->valid) {
74 | d_ptr->valid = createCollection(d_ptr->secretManager.get());
75 | }
76 |
77 | Secret secret(makeIdent(name));
78 | secret.setData(data);
79 |
80 | StoreSecretRequest ssr;
81 | ssr.setManager(d_ptr->secretManager.get());
82 | ssr.setSecretStorageType(StoreSecretRequest::CollectionSecret);
83 | ssr.setUserInteractionMode(SecretManager::SystemInteraction);
84 | ssr.setSecret(secret);
85 | ssr.startRequest();
86 | ssr.waitForFinished();
87 |
88 | return checkResult(ssr);
89 | }
90 |
91 | QByteArray OrnSecrets::data(const QString &name)
92 | {
93 | if (!d_ptr->valid) {
94 | return QByteArray();
95 | }
96 |
97 | StoredSecretRequest ssr;
98 | ssr.setManager(d_ptr->secretManager.get());
99 | ssr.setUserInteractionMode(Sailfish::Secrets::SecretManager::SystemInteraction);
100 | ssr.setIdentifier(makeIdent(name));;
101 | ssr.startRequest();
102 | ssr.waitForFinished();
103 |
104 | auto success = checkResult(ssr);
105 | if (success)
106 | {
107 | return ssr.secret().data();
108 | }
109 |
110 | return QByteArray();
111 | }
112 |
113 | bool OrnSecrets::removeCollection()
114 | {
115 | if (!d_ptr->valid) {
116 | return false;
117 | }
118 |
119 | DeleteCollectionRequest dcr;
120 | dcr.setManager(d_ptr->secretManager.get());
121 | dcr.setCollectionName(collectionName);
122 | dcr.setStoragePluginName(SecretManager::DefaultEncryptedStoragePluginName);
123 | dcr.setUserInteractionMode(Sailfish::Secrets::SecretManager::SystemInteraction);
124 | dcr.startRequest();
125 | dcr.waitForFinished();
126 | return checkResult(dcr);
127 | }
128 |
--------------------------------------------------------------------------------
/src/ornsecrets.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | struct OrnSecretsPrivate;
7 |
8 | class OrnSecrets
9 | {
10 | public:
11 | OrnSecrets();
12 | ~OrnSecrets();
13 |
14 | bool isValid() const;
15 |
16 | bool storeData(const QString &name, const QByteArray &data);
17 | QByteArray data(const QString &name);
18 | bool removeCollection();
19 |
20 | private:
21 | std::unique_ptr d_ptr;
22 | };
23 |
--------------------------------------------------------------------------------
/src/ornsecrets_p.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | struct OrnSecretsPrivate
7 | {
8 | using SecretManager = Sailfish::Secrets::SecretManager;
9 |
10 | std::unique_ptr secretManager{new SecretManager()};
11 | bool valid{false};
12 | };
13 |
--------------------------------------------------------------------------------
/src/ornssu.cpp:
--------------------------------------------------------------------------------
1 | #include "ornssu.h"
2 |
3 | #include
4 | #include
5 |
6 | OrnSsu::OrnSsu(QObject *parent)
7 | : QDBusAbstractInterface(
8 | QStringLiteral("org.nemo.ssu"),
9 | QStringLiteral("/org/nemo/ssu"),
10 | "org.nemo.ssu",
11 | QDBusConnection::systemBus(),
12 | parent
13 | )
14 | {
15 |
16 | }
17 |
18 | void OrnSsu::addRepo(const QString &alias, const QString &url) {
19 | QString method{QStringLiteral("addRepo")};
20 | qDebug().nospace()
21 | << "Calling " << this << "->" << method.toLatin1().data()
22 | << "(" << alias << ", " << url << ")";
23 | callWithArgumentList(QDBus::BlockWithGui, method, QVariantList{alias, url});
24 | }
25 |
26 | void OrnSsu::modifyRepo(int action, const QString &alias) {
27 | QString method{QStringLiteral("modifyRepo")};
28 | qDebug().nospace()
29 | << "Calling " << this << "->" << method.toLatin1().data()
30 | << "(" << action << ", " << alias << ")";
31 | callWithArgumentList(QDBus::BlockWithGui, method, QVariantList{action, alias});
32 | }
33 |
--------------------------------------------------------------------------------
/src/ornssu.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | class QDBusPendingCallWatcher;
6 |
7 | class OrnSsu : public QDBusAbstractInterface
8 | {
9 | public:
10 | explicit OrnSsu(QObject *parent = nullptr);
11 |
12 | void addRepo(const QString &alias, const QString &url);
13 | void modifyRepo(int action, const QString &alias);
14 | };
15 |
--------------------------------------------------------------------------------
/src/orntaglistitem.cpp:
--------------------------------------------------------------------------------
1 | #include "orntaglistitem.h"
2 | #include "ornutils.h"
3 | #include "ornconst.h"
4 |
5 | #include
6 |
7 |
8 | OrnTagListItem::OrnTagListItem(const QJsonObject &data)
9 | : tagId(OrnUtils::toUint(data[OrnConst::tid]))
10 | , appsCount(OrnUtils::toUint(data[OrnConst::appsCount]))
11 | , name(OrnUtils::toString(data[OrnConst::name]))
12 | {}
13 |
--------------------------------------------------------------------------------
/src/orntaglistitem.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | class QJsonObject;
6 |
7 | struct OrnTagListItem
8 | {
9 | OrnTagListItem(const QJsonObject &data);
10 |
11 | quint32 tagId;
12 | quint32 appsCount;
13 | QString name;
14 | };
15 |
--------------------------------------------------------------------------------
/src/orntagsmodel.cpp:
--------------------------------------------------------------------------------
1 | #include "orntagsmodel.h"
2 | #include "ornclient.h"
3 |
4 | #include
5 | #include
6 |
7 |
8 | OrnTagsModel::OrnTagsModel(QObject *parent)
9 | : OrnAbstractListModel(false, parent)
10 | {}
11 |
12 | QStringList OrnTagsModel::tagIds() const
13 | {
14 | return mTagIds;
15 | }
16 |
17 | void OrnTagsModel::setTagIds(const QStringList &ids)
18 | {
19 | if (mTagIds != ids)
20 | {
21 | mTagIds = ids;
22 | emit this->tagIdsChanged();
23 | this->reset();
24 | }
25 | }
26 |
27 | QVariant OrnTagsModel::data(const QModelIndex &index, int role) const
28 | {
29 | if (!index.isValid())
30 | {
31 | return QVariant();
32 | }
33 |
34 | const auto &tag = mData[index.row()];
35 | switch (role)
36 | {
37 | case TagIdRole:
38 | return tag.tagId;
39 | case AppsCountRole:
40 | return tag.appsCount;
41 | case NameRole:
42 | return tag.name;
43 | default:
44 | return QVariant();
45 | }
46 | }
47 |
48 | QHash OrnTagsModel::roleNames() const
49 | {
50 | return {
51 | { TagIdRole, "tagId" },
52 | { AppsCountRole, "appsCount" },
53 | { NameRole, "name" }
54 | };
55 | }
56 |
57 | void OrnTagsModel::fetchMore(const QModelIndex &parent)
58 | {
59 | if (parent.isValid())
60 | {
61 | return;
62 | }
63 |
64 | mFetching = true;
65 | emit this->fetchingChanged();
66 |
67 | auto client = OrnClient::instance();
68 | auto size = mTagIds.size();
69 | QString resourcePrefix(QStringLiteral("tags/"));
70 |
71 | const auto &ids = mTagIds;
72 | for (const auto &id : ids)
73 | {
74 | auto request = client->apiRequest(resourcePrefix + id);
75 | qDebug() << "Fetching data from" << request.url().toString();
76 | auto reply = client->networkAccessManager()->get(request);
77 | connect(reply, &QNetworkReply::finished, [this, client, size, reply]()
78 | {
79 | auto doc = client->processReply(reply);
80 | if (doc.isObject())
81 | {
82 | mFetchedTags.append(doc.object());
83 | if (mFetchedTags.size() == size)
84 | {
85 | this->processReply(QJsonDocument(mFetchedTags));
86 | mFetchedTags = QJsonArray();
87 | mFetching = false;
88 | emit this->fetchingChanged();
89 | }
90 | }
91 | });
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/orntagsmodel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "ornabstractlistmodel.h"
4 | #include "orntaglistitem.h"
5 |
6 | class OrnTagsModel : public OrnAbstractListModel
7 | {
8 | Q_OBJECT
9 | Q_PROPERTY(QStringList tagIds READ tagIds WRITE setTagIds NOTIFY tagIdsChanged)
10 |
11 | public:
12 | enum Role
13 | {
14 | TagIdRole = Qt::UserRole,
15 | AppsCountRole,
16 | NameRole
17 | };
18 | Q_ENUM(Role)
19 |
20 | explicit OrnTagsModel(QObject *parent = nullptr);
21 |
22 | QStringList tagIds() const;
23 | void setTagIds(const QStringList &ids);
24 |
25 | signals:
26 | void tagIdsChanged();
27 |
28 | private:
29 | QStringList mTagIds;
30 | QJsonArray mFetchedTags;
31 |
32 | // QAbstractItemModel interface
33 | public:
34 | QVariant data(const QModelIndex &index, int role) const override;
35 | QHash roleNames() const override;
36 | void fetchMore(const QModelIndex &parent) override;
37 | };
38 |
--------------------------------------------------------------------------------
/src/ornutils.cpp:
--------------------------------------------------------------------------------
1 | #include "ornutils.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 |
12 | namespace OrnUtils
13 | {
14 |
15 | QList toIntList(const QJsonValue &value)
16 | {
17 | auto array = value.toArray();
18 | QString tidKey(QStringLiteral("tid"));
19 | QList list;
20 | for (const QJsonValueRef v : array)
21 | {
22 | list << toUint(v.toObject()[tidKey]);
23 | }
24 | return list;
25 | }
26 |
27 | QString locate(const QString &filename)
28 | {
29 | auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
30 | if (!dir.exists() && !dir.mkpath(QChar('.')))
31 | {
32 | qWarning() << "Could not create local data dir" << dir.path();
33 | return QString();
34 | }
35 | return dir.absoluteFilePath(filename);
36 | }
37 |
38 | QVersionNumber systemVersion()
39 | {
40 | QSettings release(QStringLiteral("/etc/sailfish-release"), QSettings::IniFormat);
41 | return QVersionNumber::fromString(release.value(QStringLiteral("VERSION_ID")).toString());
42 | }
43 |
44 | QString desktopFile(const QString &name)
45 | {
46 | return QStandardPaths::locate(
47 | QStandardPaths::ApplicationsLocation,
48 | QStringLiteral(".desktop").prepend(name)
49 | );
50 | }
51 |
52 | } // namespace OrnUtils
53 |
--------------------------------------------------------------------------------
/src/ornutils.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | class QVersionNumber;
7 |
8 | namespace OrnUtils
9 | {
10 |
11 | inline quint32 toUint(const QJsonValue &value)
12 | {
13 | return value.toString().remove(QChar(',')).toUInt();
14 | }
15 |
16 | inline QString toString(const QJsonValue &value)
17 | {
18 | return value.toString().trimmed();
19 | }
20 |
21 | inline QDateTime toDateTime(const QJsonValue &value)
22 | {
23 | return QDateTime::fromMSecsSinceEpoch(qint64(toUint(value)) * 1000);
24 | }
25 |
26 | QList toIntList(const QJsonValue &value);
27 |
28 | QString locate(const QString &filename);
29 |
30 | QString desktopFile(const QString &name);
31 |
32 | inline QString packageName(const QString &id)
33 | {
34 | return id.section(QChar(';'), 0, 0);
35 | }
36 |
37 | inline QString packageVersion(const QString &id)
38 | {
39 | return id.section(QChar(';'), 1, 1);
40 | }
41 |
42 | inline QString packageArch(const QString &id)
43 | {
44 | return id.section(QChar(';'), 2, 2);
45 | }
46 |
47 | inline QString packageRepo(const QString &id)
48 | {
49 | return id.section(QChar(';'), 3, 3);
50 | }
51 |
52 | QVersionNumber systemVersion();
53 |
54 | inline QString stringify(bool value)
55 | {
56 | return value ? QStringLiteral("true") : QStringLiteral("false");
57 | }
58 |
59 | } // namespace OrnUtils
60 |
--------------------------------------------------------------------------------
/src/storeman.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | class QQmlEngine;
7 | class QJSEngine;
8 |
9 | class OrnApplication;
10 | class StoremanPrivate;
11 |
12 | class Storeman : public QObject
13 | {
14 | Q_OBJECT
15 | Q_DECLARE_PRIVATE(Storeman)
16 |
17 | Q_PROPERTY(bool showRecentOnStart READ showRecentOnStart WRITE setShowRecentOnStart NOTIFY showRecentOnStartChanged)
18 | Q_PROPERTY(QVariantList mainPageOrder READ mainPageOrder WRITE setMainPageOrder RESET resetMainPageOrder NOTIFY mainPageOrderChanged)
19 | Q_PROPERTY(int updateInterval READ updateInterval WRITE setUpdateInterval NOTIFY updateIntervalChanged)
20 | Q_PROPERTY(bool checkForUpdates READ checkForUpdates WRITE setCheckForUpdates NOTIFY checkForUpdatesChanged)
21 | Q_PROPERTY(bool smartUpdate READ smartUpdate WRITE setSmartUpdate NOTIFY smartUpdateChanged)
22 | Q_PROPERTY(bool showUpdatesNotification READ showUpdatesNotification WRITE setShowUpdatesNotification NOTIFY showUpdatesNotificationChanged)
23 | Q_PROPERTY(bool refreshOnSystemUpgrade READ refreshOnSystemUpgrade WRITE setRefreshOnSystemUpgrade NOTIFY refreshOnSystemUpgradeChanged)
24 | Q_PROPERTY(bool searchUnusedRepos READ searchUnusedRepos WRITE setSearchUnusedRepos NOTIFY searchUnusedReposChanged)
25 |
26 | public:
27 | enum Hint
28 | {
29 | CommentDelegateHint,
30 | CommentFieldHint,
31 | ApplicationRateAndBookmarkHint
32 | };
33 | Q_ENUM(Hint)
34 |
35 | enum MainPageItem
36 | {
37 | RecentlyUpdated,
38 | Categories,
39 | Bookmarks,
40 | Repositories,
41 | MyRepository,
42 | InstalledApps,
43 | LocalRpms,
44 | };
45 | Q_ENUM(MainPageItem)
46 |
47 | static QObject *qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);
48 |
49 | bool showRecentOnStart() const;
50 | void setShowRecentOnStart(bool value);
51 |
52 | QVariantList mainPageOrder() const;
53 | void setMainPageOrder(const QVariantList &value);
54 | void resetMainPageOrder();
55 |
56 | int updateInterval() const;
57 | void setUpdateInterval(int value);
58 |
59 | bool checkForUpdates() const;
60 | void setCheckForUpdates(bool value);
61 |
62 | bool smartUpdate() const;
63 | void setSmartUpdate(bool value);
64 |
65 | bool showUpdatesNotification() const;
66 | void setShowUpdatesNotification(bool value);
67 |
68 | bool refreshOnSystemUpgrade() const;
69 | void setRefreshOnSystemUpgrade(bool value);
70 |
71 | bool searchUnusedRepos() const;
72 | void setSearchUnusedRepos(bool value);
73 |
74 | Q_INVOKABLE static bool fileExists(const QString &filePath);
75 | Q_INVOKABLE static bool removeFile(const QString &filePath);
76 |
77 | Q_INVOKABLE bool showHint(Storeman::Hint hint);
78 | Q_INVOKABLE void setHintShowed(Storeman::Hint hint);
79 |
80 | Q_INVOKABLE OrnApplication *cachedApp(quint32 appId);
81 |
82 | public slots:
83 | void resetUpdatesTimer();
84 |
85 | signals:
86 | void showRecentOnStartChanged();
87 | void mainPageOrderChanged();
88 | void updateIntervalChanged();
89 | void checkForUpdatesChanged();
90 | void smartUpdateChanged();
91 | void showUpdatesNotificationChanged();
92 | void refreshOnSystemUpgradeChanged();
93 | void searchUnusedReposChanged();
94 | void updatesNotification(bool show, quint32 replaceId);
95 | void recentAppsChanged();
96 |
97 | private slots:
98 | void refreshRepos();
99 | void onUpdatablePackagesChanged();
100 | void startUpdatesTimer();
101 | void checkSystemVersion();
102 |
103 | private:
104 | explicit Storeman(QObject *parent = nullptr);
105 | };
106 |
--------------------------------------------------------------------------------
/src/storeman_p.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "storeman.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 |
11 | class StoremanPrivate : public QObjectPrivate
12 | {
13 | Q_DECLARE_PUBLIC(Storeman)
14 |
15 | void refreshRepos();
16 |
17 | QSettings settings;
18 | QTimer updatesTimer;
19 | QCache appsCache;
20 | };
21 |
--------------------------------------------------------------------------------
/translations/README.md:
--------------------------------------------------------------------------------
1 | # Translations (l10n / i18n)
2 |
3 | You can help localising Storeman to your language using [Transifex](https://app.transifex.com/mentaljam/harbour-storeman) or [Qt Linguist](https://doc.qt.io/qt-5/qtlinguist-index.html).
4 |
5 | Note that for Storeman principally translations designated with a country code only (e.g. `pt`) shall be created and maintained. Only if a complete and well maintained translation for the sole country code exists, a country specific variant with a locale will be accepted (e.g. `nl_BE`).
6 |
7 | [Transifex](https://app.transifex.com/mentaljam/harbour-storeman) is the preferred way of submitting translations. Please do not send pull requests (PRs) with translations directly to GitHub, if you have a Transifex account.
8 |
9 | If you do not want to use Atlassian's Transifex, alternatives are [Qt Linguist](https://doc.qt.io/qt-5/linguist-translators.html) or to perform this manually, which is tedious and error prone, hence only suitable for small changes. The resulting changes must be submitted as a pull request, unfortunately.
10 |
11 | ### Testing translations
12 |
13 | Note that translations for Storeman are utilising *ID based* Qt `.ts` files. Hence, to compile a translation file for testing, the `lrelease` command must be executed with the option `-idbased` to convert the translation files (`.ts` files) into Qt message files (`.qm` files), either from [within Qt Linguist](https://doc.qt.io/qtcreator/creator-editor-external.html) or directly [at the command line](https://doc.qt.io/qt-5/linguist-manager.html):
14 | ```
15 | lrelease -idbased harbour-storeman.ts
16 | ```
17 | If you want to test your translation before publishing, you should compile it and copy the resulting `.qm` file(s) to (requires root privileges):
18 | ```
19 | /usr/share/harbour-storeman/translations
20 | ```
21 | Storeman tries to automatically load a translation file according to your system locale setting. You can also run the application with a selected locale from the terminal. For example, for the Swedish language the command is:
22 | ```
23 | export LANG=sv; harbour-storeman
24 | ```
25 |
26 | ### Updating the source `.ts` file with source strings from source code
27 |
28 | Developers and release managers can use the `lupdate` process, either from [within Qt Linguist](https://doc.qt.io/qtcreator/creator-editor-external.html) or directly [at the command line](https://doc.qt.io/qt-5/linguist-manager.html) (mind to [include all files with translatable strings](https://github.com/storeman-developers/harbour-storeman/pull/431#issuecomment-1659024529), e.g. by `lupdate qml/ src/ *.desktop -ts translations/harbour-storeman.ts`), or tediously perform this manually, which hence is only suitable for small changes.
29 |
30 | ---------------------------------------------
31 |
32 | **Note**: When updating this README, mind to also update [its counterpart for the SailfishOS:Chum GUI app](https://github.com/sailfishos-chum/sailfishos-chum-gui//blob/main/translations/README.md).
33 |
--------------------------------------------------------------------------------
/translations/translations.pri:
--------------------------------------------------------------------------------
1 | TRANSLATIONS += \
2 | translations/harbour-storeman.ts \
3 | translations/harbour-storeman-cs.ts \
4 | translations/harbour-storeman-da.ts \
5 | translations/harbour-storeman-de.ts \
6 | translations/harbour-storeman-el.ts \
7 | translations/harbour-storeman-es.ts \
8 | translations/harbour-storeman-et.ts \
9 | translations/harbour-storeman-fi.ts \
10 | translations/harbour-storeman-fr.ts \
11 | translations/harbour-storeman-hu.ts \
12 | translations/harbour-storeman-it.ts \
13 | translations/harbour-storeman-nl.ts \
14 | translations/harbour-storeman-nl_BE.ts \
15 | translations/harbour-storeman-no.ts \
16 | translations/harbour-storeman-pl.ts \
17 | translations/harbour-storeman-pt.ts \
18 | translations/harbour-storeman-ru.ts \
19 | translations/harbour-storeman-sk.ts \
20 | translations/harbour-storeman-sl.ts \
21 | translations/harbour-storeman-sv.ts \
22 | translations/harbour-storeman-tt.ts \
23 | translations/harbour-storeman-zh.ts
24 |
25 | qm.input = TRANSLATIONS
26 | qm.output = translations/${QMAKE_FILE_BASE}.qm
27 | qm.commands = @echo "compiling ${QMAKE_FILE_NAME}"; \
28 | lrelease -idbased -silent ${QMAKE_FILE_NAME} -qm ${QMAKE_FILE_OUT}
29 | qm.CONFIG = target_predeps no_link
30 |
31 | QMAKE_EXTRA_COMPILERS += qm
32 |
33 | translations.files = $$OUT_PWD/translations/*.qm
34 | translations.path = $$PREFIX/share/$$TARGET/translations
35 | translations.CONFIG += no_check_exist
36 |
37 | INSTALLS += translations
38 |
--------------------------------------------------------------------------------