A brief lyrical introduction - in 2017, I happened to be very closely interested in meditations. This was facilitated by a whole chain of events, favorable and not very. For many years I have been interested in and practicing lucid dreams, but before that I did not have to engage in specific meditations in their canonical form. These days, manystories start at the bar (s)Hobbies begin with a search on Google, so I started that way. Almost immediately, the top-most popular meditation applications were found - Calm and Headspace.
The first served as a good starting point (excellent educational meditations for beginners), the second I did not find useful for myself, I did not like the presentation. Both pushed away with their paid (and I must say very expensive for the Russian Federation) tariff plans. Maybe I just do not belong to the category of people who need to pay in order to encourage themselves to do something :) Continuing to study Google play, I came across two free applications that are close to me in spirit. The first is “Let's Meditate” - I still use it, the second will be discussed in the body of the article.
application
So, after quite a long search, a completely inconspicuous application was found, then it was called, if I am not mistaken, "Meditation. Antonov Alexander." As it turned out, it was possible to listen to four author’s meditations in it, recorded and composed, in fact, by Alexander, whom we later met and made friends with. He assembled the application literally from improvised tools on his own, it was something like a makeshift SPA using WebView without any frameworks, practically in bare HTML and minimally in Java. It looked so-so, and some functions were simply absent (for example, you could not navigate through the recording, but only turn it on from the beginning). Since I really liked the content itself, I offered Alexander my free help in upgrading the application, so that, so to speak, "to give something back “on the basis of” they helped me, I will help. ”In the body of the article I will try to tell you what problems we encountered during development, what decisions were made, and what happened in the end! I hope that the individual recipes of the article will be useful to anyone, but interestingly interesting :)
, :
Qt
— (), PHP/HTML. , , , Qt, :
- Qt ( Symbian, MeeGo, Ubuntu Phone Android);
- , ;
- QML, C++. , — JavaScript-like , ;
- iOS ( ).
UI
, , . , , . , . ( ):

"" anchors, Row, Column Repeater. , UI. , ( ):
Button {
id: mainButton
anchors {
left: parent.left
right: parent.right
}
height: btnLayout.height + 30
Material.background: "white"
onClicked: stackView.push(Qt.resolvedUrl("qrc:/qml/MeditationListPage.qml"))
Column {
id: btnLayout
spacing: 10
anchors {
top: parent.top
topMargin: 15
left: parent.left
right: parent.right
margins: 10
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: (mainButton.width - 4 * 50) / 6
Repeater {
model: meditationModel
RoundedIcon {
source: Qt.resolvedUrl("qrc:/img/my%1.png".arg(model.index))
color: model.color
width: 50
height: 50
}
}
}
Label {
text: qsTr("")
font.pointSize: 14
color: "dimgrey"
anchors.horizontalCenter: parent.horizontalCenter
}
Label {
text: " , "
anchors {
left: parent.left
right: parent.right
}
horizontalAlignment: Text.AlignHCenter
Material.foreground: Material.Grey
font.pixelSize: 12
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
}
}
UI , HDPI Qt . , Qt: QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
. " ".
Material design, Android Qt Quick Controls 2 . https://doc.qt.io/qt-5/qtquickcontrols2-material.html. , Material design. UI , attached property Material.accent
.

mp3- , QRC. 10-15 . — , 15 . , , pro-:
CONFIG += resources_big
, , , .
, " ". Shorts, , . DarkModeShader.qml
:
ShaderEffect {
fragmentShader: "
uniform lowp sampler2D source;
uniform lowp float qt_Opacity;
varying highp vec2 qt_TexCoord0;
void main() {
lowp vec4 p = texture2D(source, qt_TexCoord0);
p.r = min(0.8, (1.0 - p.r) * 0.8 + 0.1);
p.g = min(0.8, (1.0 - p.g) * 0.8 + 0.1);
p.b = min(0.8, (1.0 - p.b) * 0.8 + 0.1);
gl_FragColor = vec4(vec3(dot(p.rgb, vec3(0.299, 0.587, 0.114))), p.a) * qt_Opacity;
}
"
}
:
StackView {
// ...
layer.effect: DarkModeShader { }
layer.enabled: optionsKeeper.isNightMode
}
.. , isNightMode
. Qt / ( , ).
, — . - , — !
Qt Multimedia, Audio
. mp3, , — , :
Audio {
id: audioPlayback
source: meditAudioSource
}
// ...
Slider {
anchors {
left: parent.left
right: playBtn.left
verticalCenter: parent.verticalCenter
}
from: 0
to: audioPlayback.duration
value: audioPlayback.position
onMoved: audioPlayback.seek(value)
Material.accent: meditColor
}
Settings
Qt.labs.settings
(, , labs):
import Qt.labs.settings 1.0
// ... boilerplate- , .
property Settings settings: Settings {
property bool isNightMode: false
}
2018 , .
, , 2020, , - , — ( ). , — — :)
, JSON- . . HTTP- QML- XMLHttpRequest
( JSON QML). .
. QML LocalStorage, SQLite. JS-, QML, :
// databasemodule.js
.pragma library // I hope this will prevent the waste of memory.
.import QtQuick.LocalStorage 2.0 as SQL
function getMeditations() {
...
}
// TransferManager.qml
import "databasemodule.js" as DB
...
var syncedItems = DB.getMeditations()
:
var db = SQL.LocalStorage.openDatabaseSync("AMeditation", "", "Main DB", 100000)
...
db.transaction(function(tx) {
dbResult = tx.executeSql("SELECT * FROM meditations")
console.log("meditations SELECTED: ", dbResult.rows.length)
})
.. , transaction
callback. ( , Qt ).
. openDatabaseSync
. , ( , ). . , , , . [" ", " ", " "]:
var migrations = [
{'from': "", 'to': "1.0", 'ops': function(transaction) {
transaction.executeSql("CREATE TABLE meditations ( \
id INTEGER PRIMARY KEY
...
status TEXT);")
}}
,{'from': "1.0", 'to': "1.1", 'ops': function(transaction) {
transaction.executeSql("ALTER TABLE meditations ADD quality TEXT;")
}}
]
, ( — , - ).
C++
QML, C++. . Ubuntu Phone. . QML — . C++- QML :
engine.rootContext()->setContextProperty("networkManager", new NetworkManager());
QML - :
// .
var isSucces = networkManager.download(downloadUrl, currentDownload.localUrl)
...
// .
Connections {
target: networkManager
onDownloadOperationProgress: {
d.currentDownload.current = current
d.currentDownload.total = total
}
...
}
C++-, QML- singletone :
class CppUtils : public QObject
{
Q_OBJECT
public:
explicit CppUtils(QObject *parent = nullptr);
~CppUtils();
Q_INVOKABLE bool removeFile(const QString& fileName) const;
static QObject *cppUtilsSingletoneProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
};
qmlRegisterSingletonType<CppUtils>("AMeditation.CppUtils", 1, 0, "CppUtils", CppUtils::cppUtilsSingletoneProvider);
.. , cppUtilsSingletoneProvider
, Q_INVOKABLE — "" QML. QML :
import AMeditation.CppUtils 1.0
// ...
CppUtils.removeFile(cd.localUrl)
, - . — . , - , . , . QML (QNetworkAccessManager). . :
class CachingNetworkAccessManager : public QNetworkAccessManager
{
public:
CachingNetworkAccessManager(QObject *parent = 0);
protected:
QNetworkReply* createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData = 0);
};
class CachingNetworkManagerFactory : public QQmlNetworkAccessManagerFactory
{
public:
CachingNetworkManagerFactory();
QNetworkAccessManager *create(QObject *parent);
};
QNetworkReply* CachingNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
{
QNetworkRequest req(request);
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork);
return QNetworkAccessManager::createRequest(op, req, outgoingData);
}
QNetworkAccessManager *CachingNetworkManagerFactory::create(QObject *parent) {
QNetworkAccessManager* manager = new CachingNetworkAccessManager(parent);
QNetworkDiskCache* cache = new QNetworkDiskCache(manager);
cache->setCacheDirectory(QString("%1/network").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
manager->setCache(cache);
return manager;
}
.. setCache
, createRequest
. — , .
Android
SDK NDK. Linux- , Windows - (, , QtCreator 4.12 NDK, ). NDK Clang. arm_v7 (32 ), arm_v8a (64 ; Google play 2019 ). Google play.
, , .
- Qt pet-, Android. ;
- AMeditation ( UI " 2.1. ") 10+ 250 , ( , , ).
- , , !
- iOS .
In general, developing on Qt is fun, doing something for free and for the soul too!
PS Dear Khabrovsk residents, I hope you don’t count it as advertising - the application is fundamentally free and non-commercial, I wanted to tell about it and the history of its creation to those who were interested, just like to share successfully found approaches to development.
PSS The application has its twin version 1.1 with older entries, which has another 5k + downloads and 100 reviews. We’ll probably get it out of the store soon.