Qt auf Android: Wie wir einer Meditationsanwendung fĂŒr Autoren ein zweites Leben geschenkt haben

Eine kurze lyrische EinfĂŒhrung - 2017 war ich zufĂ€llig sehr stark an Meditationen interessiert. Dies wurde durch eine ganze Kette von Ereignissen erleichtert, gĂŒnstig und nicht sehr. Seit vielen Jahren interessiere ich mich fĂŒr klare TrĂ€ume und praktiziere sie, aber vorher musste ich mich nicht speziell mit Meditationen in ihrer kanonischen Form befassen. In diesen Tagen vieleGeschichten beginnen an der / den Bar (s)Hobbys beginnen mit einer Suche bei Google, also habe ich so angefangen. Fast sofort wurden die beliebtesten Meditationsanwendungen gefunden - Ruhe und Kopfraum.


Das erste diente als guter Ausgangspunkt (ausgezeichnete pĂ€dagogische Meditationen fĂŒr AnfĂ€nger), das zweite fand ich fĂŒr mich selbst nicht nĂŒtzlich, die PrĂ€sentation gefiel mir nicht. Beide haben ihre bezahlten (und fĂŒr die Russische Föderation sehr teuren) TarifplĂ€ne verdrĂ€ngt. Vielleicht gehöre ich einfach nicht zu der Kategorie von Leuten, die zahlen mĂŒssen, um sich zu etwas zu ermutigen :) Als ich Google Play weiter studierte, stieß ich auf zwei kostenlose Anwendungen, die mir im Geiste nahe stehen. Das erste ist "Lass uns meditieren" - ich benutze es immer noch, das zweite wird im Hauptteil des Artikels besprochen.


Anwendung


Nach einer langen Suche wurde eine völlig unauffĂ€llige Anwendung gefunden, die, wenn ich mich nicht irre, "Meditation. Antonov Alexander" genannt wurde. Wie sich herausstellte, war es möglich, vier Meditationen des Autors zu hören, die tatsĂ€chlich von Alexander aufgenommen und komponiert wurden, mit dem wir uns spĂ€ter trafen und anfreundeten. Er stellte die Anwendung buchstĂ€blich aus improvisierten Tools zusammen. Es war so etwas wie ein hausgemachtes SPA, das WebView ohne Frameworks verwendete, praktisch in reinem HTML und minimal in Java. Es sah mittelmĂ€ĂŸig aus und einige Funktionen fehlten einfach (zum Beispiel konnte man nicht durch die Aufnahme navigieren, sondern sie nur von Anfang an einschalten). Da mir der Inhalt selbst sehr gut gefallen hat, habe ich Alexander meine kostenlose Hilfe bei der Aktualisierung der Anwendung angeboten, so dass "Um etwas zurĂŒckzugeben, "auf der Grundlage", dass sie mir geholfen haben, werde ich helfen. "Im Hauptteil des Artikels werde ich versuchen, Ihnen zu sagen, auf welche Probleme wir wĂ€hrend der Entwicklung gestoßen sind, welche Entscheidungen getroffen wurden und was am Ende passiert ist! Ich hoffe, dass die einzelnen Rezepte des Artikels sein werden nĂŒtzlich fĂŒr jedermann, aber interessant interessant :)



, :


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

Im Allgemeinen macht es Spaß, sich auf Qt zu entwickeln und etwas kostenlos und auch fĂŒr die Seele zu tun!


PS Liebe Einwohner von Chabrowsk, ich hoffe, Sie zÀhlen es nicht als Werbung - die Anwendung ist grundsÀtzlich kostenlos und nicht kommerziell. Ich wollte Interessierten davon und von der Entstehungsgeschichte erzÀhlen, nur um erfolgreich gefundene EntwicklungsansÀtze zu teilen.
PSS Die Anwendung hat ihre Doppelversion 1.1 mit Àlteren EintrÀgen, die weitere 5k + Downloads und 100 Bewertungen enthÀlt. Wir werden es wahrscheinlich bald aus dem Laden holen.



All Articles