Qt on Android: how we gave a second life to an author meditation application

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 :)



, :


  • UI UX

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 :


// cpputils.h
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);
};

// main.cpp
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). . :


// cachingnetworkmanagerfactory.h
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);
};

// cachingnetworkmanagerfactory.cpp

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.



All Articles