Qt sur Android: comment nous avons donné une seconde vie à une application de méditation d'auteur

Une brève introduction lyrique - en 2017, je me suis avéré être très intéressé par les méditations. Cela a été facilité par toute une chaîne d'événements, favorables et peu nombreux. Pendant de nombreuses années, je me suis intéressé et pratiqué les rêves lucides, mais avant cela, je n'avais pas à m'engager dans des méditations spécifiques sous leur forme canonique. De nos jours, beaucouples histoires commencent au (x) bar (s)Les loisirs commencent par une recherche sur Google, alors j'ai commencé comme ça. Presque immédiatement, les applications de méditation les plus populaires ont été trouvées - Calme et Headspace.


Le premier a servi de bon point de départ (excellentes méditations pédagogiques pour les débutants), le second que je n'ai pas trouvé utile pour moi, je n'ai pas aimé la présentation. Tous deux ont repoussé leurs plans tarifaires payés (et je dois dire très cher pour la Fédération de Russie). Peut-être que je n'appartiens tout simplement pas à la catégorie des personnes qui doivent payer pour s'encourager à faire quelque chose :) Continuant à étudier Google Play, je suis tombé sur deux applications gratuites qui me sont proches dans l'esprit. Le premier est «méditons» - je l'utilise toujours, le second sera discuté dans le corps de l'article.


application


Ainsi, après une recherche assez longue, une application complètement discrète a été trouvée, puis elle a été appelée, si je ne me trompe pas, "Méditation. Antonov Alexander." En fin de compte, il a été possible d’écouter quatre méditations d’auteurs, enregistrées et composées, en fait, par Alexandre, que nous avons ensuite rencontré et fait des amis. Il a assemblé l'application littéralement à partir d'outils improvisés par lui-même, c'était quelque chose comme un SPA fait maison utilisant WebView sans aucun cadre, pratiquement en HTML nu et minimalement en Java. Cela semblait moyen et certaines fonctions étaient tout simplement absentes (par exemple, vous ne pouviez pas naviguer dans l'enregistrement, mais l'activer uniquement depuis le début). Comme j'ai vraiment aimé le contenu lui-même, j'ai offert à Alexander mon aide gratuite pour la mise à niveau de l'application, de sorte que, pour ainsi dire, "pour donner quelque chose "sur la base de" ils m'ont aidé, je vais aider. "Dans le corps de l'article, je vais essayer de vous dire quels problèmes nous avons rencontrés dans le développement, quelles décisions ont été prises et ce qui s'est passé à la fin! J'espère que les recettes individuelles de l'article seront utile à tout le monde, mais intéressant intéressant :)



, :


  • 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 .

En général, développer sur Qt est amusant, faire quelque chose gratuitement et pour l'âme aussi!


PS Chers résidents de Khabrovsk, j'espère que vous ne le considérez pas comme de la publicité - l'application est fondamentalement gratuite et non commerciale, je voulais en parler et l'histoire de sa création à ceux qui étaient intéressés, tout comme partager avec succès des approches de développement trouvées.
PSS L'application a sa version jumelle 1.1 avec des entrées plus anciennes, qui a encore 5k + téléchargements et 100 critiques. Nous le sortirons probablement bientôt du magasin.



All Articles