Qt على Android: كيف أعطينا حياة ثانية لتطبيق تأمل المؤلف

مقدمة غنائية موجزة - في عام 2017 ، كنت مهتمًا جدًا بالتأملات. وقد تم تسهيل ذلك من خلال سلسلة كاملة من الأحداث ، مواتية وليس للغاية. لسنوات عديدة كنت مهتمًا وممارسة الأحلام الواضحة ، ولكن قبل ذلك لم يكن عليّ التعامل بشكل خاص مع التأملات في شكلها القانوني. هذه الأيام ، كثيرتبدأ القصص من البارتبدأ الهوايات ببحث على Google ، لذلك بدأت بهذه الطريقة. على الفور تقريبًا ، تم العثور على أشهر تطبيقات التأمل - Calm and Headspace.


خدم الأول كنقطة بداية جيدة (تأملات تعليمية ممتازة للمبتدئين) ، والثاني لم أجد مفيدًا لنفسي ، ولم يعجبني العرض التقديمي. دفع كلاهما بخطط التعريفة المدفوعة (ويجب أن أقول مكلفة للغاية بالنسبة للاتحاد الروسي). ربما أنا لا أنتمي إلى فئة الأشخاص الذين يحتاجون إلى الدفع من أجل تشجيع أنفسهم على القيام بشيء :) بمواصلة دراسة Google play ، صادفت تطبيقين مجانيين قريبين مني من الناحية الروحية. الأول هو "دعنا نتأمل" - ما زلت أستخدمه ، والثاني سيتم مناقشته في نص المقالة.


تطبيق


لذا ، بعد بحث طويل جدًا ، تم العثور على تطبيق غير واضح تمامًا ، ثم تم تسميته ، إذا لم أكن مخطئًا ، "التأمل. أنطونوف ألكسندر". كما اتضح ، كان من الممكن الاستماع إلى أربعة تأملات للمؤلف فيها ، وتسجيلها وتأليفها ، في الواقع ، من قبل ألكسندر ، الذي التقينا به لاحقًا وتكوين صداقات معه. قام بتجميع التطبيق حرفياً من أدوات مرتجلة بمفرده ، وكان شيئًا مثل SPA محلي الصنع باستخدام WebView بدون أي أطر ، عمليًا في HTML عارية وبحد أدنى في Java. بدا الأمر كذلك ، وكانت بعض الوظائف غائبة ببساطة (على سبيل المثال ، لا يمكنك التنقل خلال التسجيل ، ولكن فقط قم بتشغيله من البداية). نظرًا لأنني أعجبت حقًا بالمحتوى نفسه ، فقد عرضت على ألكسندر مساعدتي المجانية في ترقية التطبيق ، لذلك ، إذا جاز التعبير ، "لإرجاع شيء ما "على أساس" ساعدوني ، سأساعد. "في نص المقالة سأحاول إخبارك بالمشكلات التي واجهناها أثناء التطوير ، وما هي القرارات التي تم اتخاذها ، وما حدث في النهاية! آمل أن تكون الوصفات الفردية للمقال مفيد لأي شخص ، ولكن المثير للاهتمام :)



, :


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

بشكل عام ، التطوير على Qt أمر ممتع ، القيام بشيء مجانًا وللروح أيضًا!


عزيزي سكان خابروفسك ، آمل ألا تحسبه كإعلان - التطبيق مجاني بشكل أساسي وغير تجاري ، أردت أن أخبرك عن ذلك وتاريخ إنشائه لأولئك المهتمين ، تمامًا مثل مشاركة طرق التطوير التي تم العثور عليها بنجاح.
PSS يحتوي التطبيق على الإصدار التوأم 1.1 مع الإدخالات الأقدم ، والتي تحتوي على تنزيلات 5k + و 100 مراجعة. من المحتمل أن نخرجه من المتجر قريبًا.



All Articles