diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .gitmodules | 6 | ||||
-rw-r--r-- | Catalog.qml | 28 | ||||
-rw-r--r-- | README.md | 48 | ||||
-rw-r--r-- | controller.cxx | 55 | ||||
-rw-r--r-- | controller.hxx | 21 | ||||
-rw-r--r-- | handler.c | 62 | ||||
-rw-r--r-- | images/onboarding-illustration-1.png | bin | 0 -> 64816 bytes | |||
m--------- | larva | 0 | ||||
-rw-r--r-- | larva.qrc | 5 | ||||
-rw-r--r-- | main.cxx | 12 | ||||
-rw-r--r-- | main.qml | 46 | ||||
-rw-r--r-- | namatoko.pro | 50 | ||||
-rw-r--r-- | namatoko.qrc | 8 | ||||
m--------- | qicpos | 0 | ||||
-rw-r--r-- | qicpos.qrc | 5 | ||||
-rw-r--r-- | qtquickcontrols2.conf | 7 | ||||
-rw-r--r-- | registration.c | 34 |
18 files changed, 391 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e79efa9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +*.pro.* +*.swp +build* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b1eb6f3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "larva"] + path = larva + url = git://darapsa.org/larva.git +[submodule "qicpos"] + path = qicpos + url = git://darapsa.org/qicpos.git diff --git a/Catalog.qml b/Catalog.qml new file mode 100644 index 0000000..be144c5 --- /dev/null +++ b/Catalog.qml @@ -0,0 +1,28 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import "qicpos" + +Rectangle { + color: "#0a0a0a" + ListView { + anchors.fill: parent + objectName: "catalog" + model: catalog + spacing: 16 + delegate: ProductForm { + width: 328 + height: 132 + color: "#2a2a2a" + anchors.horizontalCenter: parent.horizontalCenter + previewImage { + width: 64 + height: 64 + source: imageDir + "thumb/" + image + } + nameLabel.text: description + priceLabel.text: price + } + + ScrollBar.horizontal: ScrollBar {} + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..a96f32c --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Namatoko + +# Building + +## Getting + +```sh +$ git clone interch@darapsa.co.id:/usr/local/git/namatoko.git +$ git submodule init +$ git submodule update +$ cd namatoko +$ mkdir build +$ cd build +``` + +## Configuring for various target hosts (with optional debugging), compiling, and linking + +```sh +$ /opt/Qt/5.15.2/wasm_32/bin/qmake ../namatoko.pro -spec wasm-emscripten CONFIG+='debug qml_debug' SAMPLEURL=https://darapsa.com IMAGE_DIR=/images +$ emmake make +``` + +or + +```sh +$ /opt/Qt/5.15.2/android/bin/qmake ../namatoko.pro -spec android-clang CONFIG+='debug qml_debug' ANDROID_NDK_PATH=/opt/android-sdk-update-manager/ndk/21.3.6528147 ANDROID_NDK_HOST=linux-x86_64 ANDROID_TARGET_ARCH=arm64-v8a QT_ANDROID_LIBDIR=/opt/Qt/5.15.2/android/lib API=21 CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt ANDROID_ABIS=arm64-v8a SAMPLEURL=https://darapsa.com IMAGE_DIR=/images +$ make +``` + +or so on. + +## Producing bundle + +For Android: + +```sh +$ make install INSTALL_ROOT=android-build +$ export ANDROID_SDK_ROOT=/opt/android-sdk-update-manager +$ /opt/Qt/5.15.2/android/bin/androiddeployqt --input android-namatoko-deployment-settings.json --output android-build --android-platform android-30 +``` + +and if on FreeBSD: + +```sh +$ cd android-build +$ echo "android.aapt2FromMavenOverride = $ANDROID_SDK_ROOT/build-tools/28.0.3/aapt2" >> gradle.properties +./gradlew assembleDebug +``` diff --git a/controller.cxx b/controller.cxx new file mode 100644 index 0000000..2e95c09 --- /dev/null +++ b/controller.cxx @@ -0,0 +1,55 @@ +#include <QtQml> +#include <qicclient/admin.hxx> +#include "controller.hxx" + +extern "C" { + void sign_up(char const*, char const*); + struct icclient_catalog* catalog_data(char const*); +} + +Controller::Controller(QObject* parent) : + QObject{parent}, + catalog{nullptr} +{ +#ifdef __ANDROID__ + QString cert{CA_BUNDLE}; + QString path{QDir{QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)}.absolutePath() + % cert.remove(0, cert.lastIndexOf("/"))}; + QFile{"assets:" % cert}.copy(path); +#endif + interchange = new Client{SAMPLEURL, IMAGE_DIR +#ifdef __ANDROID__ + , path.toLatin1().constData() +#endif + }; + auto engine = static_cast<QQmlApplicationEngine*>(parent); + engine->load(QUrl{QStringLiteral("qrc:/main.qml")}); + auto window = engine->rootObjects()[0]; + window->setProperty("imageDir", SAMPLEURL"/images/"); + connect(window, SIGNAL(signUp(QString)), this, SIGNAL(signUp(QString))); + connect(this, &Controller::signUp, [/*this,*/ +#ifdef __ANDROID__ + &path +#endif + ](QString const& brand) { + sign_up(brand.toLatin1().constData(), +#ifdef __ANDROID__ + path.toLatin1().constData() +#else + nullptr +#endif + ); +// interchange->catalog(brand); + }); + connect(interchange, &Client::gotCatalog, [this,engine,window](QString const& response) { + catalog = new Catalog{catalog_data(response.toLatin1().constData())}; + engine->rootContext()->setContextProperty("catalog", catalog); + QMetaObject::invokeMethod(window, "pushCatalog"); + }); +} + +Controller::~Controller() +{ + if (catalog) delete catalog; + delete interchange; +} diff --git a/controller.hxx b/controller.hxx new file mode 100644 index 0000000..b073a67 --- /dev/null +++ b/controller.hxx @@ -0,0 +1,21 @@ +#ifndef CONTROLLER_HXX +#define CONTROLLER_HXX + +#include <qicclient.hxx> + +using namespace QICClient; + +class Controller : public QObject +{ + Q_OBJECT + public: + Controller(QObject* parent = nullptr); + ~Controller(); + signals: + void signUp(QString const& name); + private: + Client* interchange; + Catalog* catalog; +}; + +#endif diff --git a/handler.c b/handler.c new file mode 100644 index 0000000..8e118f7 --- /dev/null +++ b/handler.c @@ -0,0 +1,62 @@ +#include <stdbool.h> +#include <tidy.h> +#include <tidybuffio.h> +#include <icclient.h> + +static void recurse_catalog(TidyDoc doc, TidyNode tnod, struct icclient_catalog **catalog) +{ + for (TidyNode child = tidyGetChild(tnod); child; child = tidyGetNext(child)) { + ctmbstr name = tidyNodeGetName(child); + if (!name) + continue; + if (strcmp(name, "img")) { + recurse_catalog(doc, child, catalog); + continue; + } + static const char *prefix = IMAGE_DIR"/thumb/"; + size_t prefix_len = strlen(prefix); + bool bail = false; + for (TidyAttr attr = tidyAttrFirst(child); attr; attr = tidyAttrNext(attr)) + if (!strcmp(tidyAttrName(attr), "src") && strncmp(tidyAttrValue(attr), prefix, prefix_len)) { + bail = true; + break; + } + if (bail) + continue; + struct icclient_product *product = malloc(sizeof(struct icclient_product)); + memset(product, '\0', sizeof(struct icclient_product)); + for (TidyAttr attr = tidyAttrFirst(child); attr; attr = tidyAttrNext(attr)) { + name = tidyAttrName(attr); + ctmbstr value = tidyAttrValue(attr); + if (!strcmp(name, "src")) { + size_t len = strlen(value) - prefix_len; + product->image = malloc(len + 1); + strncpy(product->image, value + prefix_len, len + 1); + } else if (!strcmp(name, "alt")) { + product->description = malloc(strlen(value) + 1); + strcpy(product->description, value); + } else if (!strcmp(name, "title")) { + product->sku = malloc(strlen(value + 1)); + strcpy(product->sku, value); + } + } + (*catalog)->length++; + *catalog = realloc(*catalog, sizeof(struct icclient_catalog) + + sizeof(struct icclient_product *[(*catalog)->length])); + (*catalog)->products[(*catalog)->length - 1] = product; + } +} + +struct icclient_catalog *catalog_data(const char *response) +{ + TidyDoc tdoc = tidyCreate(); + TidyBuffer output = {0}; + tidyParseString(tdoc, response); + tidySaveBuffer(tdoc, &output); + struct icclient_catalog *catalog = malloc(sizeof(struct icclient_catalog)); + catalog->length = 0; + recurse_catalog(tdoc, tidyGetRoot(tdoc), &catalog); + tidyBufFree(&output); + tidyRelease(tdoc); + return catalog; +} diff --git a/images/onboarding-illustration-1.png b/images/onboarding-illustration-1.png Binary files differnew file mode 100644 index 0000000..c09f062 --- /dev/null +++ b/images/onboarding-illustration-1.png diff --git a/larva b/larva new file mode 160000 +Subproject 4f7f7f1a79ea43aeb2266fca27d27346a78125a diff --git a/larva.qrc b/larva.qrc new file mode 100644 index 0000000..61307a5 --- /dev/null +++ b/larva.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>larva/features/EmailForm.ui.qml</file> + </qresource> +</RCC> diff --git a/main.cxx b/main.cxx new file mode 100644 index 0000000..9de5dab --- /dev/null +++ b/main.cxx @@ -0,0 +1,12 @@ +#include <QGuiApplication> +#include <QQmlApplicationEngine> +#include "controller.hxx" + +int main(int argc, char* argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app{argc, argv}; + QQmlApplicationEngine engine; + Controller controller{&engine}; + return app.exec(); +} diff --git a/main.qml b/main.qml new file mode 100644 index 0000000..3d40e75 --- /dev/null +++ b/main.qml @@ -0,0 +1,46 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import "larva/features" + +ApplicationWindow { + property string imageDir + signal signUp(string brand) + function pushCatalog() { + stack.push("Catalog.qml") + } + id: window + width: 360 + height: 640 + visible: true + StackView { + id: stack + anchors.fill: parent + initialItem : Column { + Image { + width: parent.width + fillMode: Image.PreserveAspectFit + source: "images/onboarding-illustration-1.png" + } + EmailForm { + anchors.bottom: parent.bottom + width: parent.width + backButton.visible: false + instructionLabel { + text: qsTr("Cukup masukkan nama toko Anda, et voilĂ !") + font.pointSize: 18 + } + emailTextField { + placeholderText: "Coba: Hardware, Ladders, Measuring Tools, Safety Equipment, Hand Tools, Painting Supplies, atau Tool Storage" + placeholderTextColor: "#888888" + color: "#000000" + onTextChanged: if (!emailTextField.text || !loginButton.enabled) + loginButton.enabled = !loginButton.enabled + } + loginButton { + text: qsTr("Mulai berjualan!") + onClicked: signUp(emailTextField.text) + } + } + } + } +} diff --git a/namatoko.pro b/namatoko.pro new file mode 100644 index 0000000..b2830b8 --- /dev/null +++ b/namatoko.pro @@ -0,0 +1,50 @@ +QT += quickcontrols2 +DEFINES += \ + CA_BUNDLE=\\\"$$CA_BUNDLE\\\" \ + SAMPLEURL=\\\"$$SAMPLEURL\\\" \ + IMAGE_DIR=\\\"$$IMAGE_DIR\\\" +debug: DEFINES += DEBUG +HEADERS += controller.hxx +SOURCES += \ + main.cxx \ + controller.cxx \ + registration.c \ + handler.c +RESOURCES += \ + namatoko.qrc \ + qicpos.qrc \ + larva.qrc \ + larva/material-design-icons.qrc +wasm { + QT += svg + QMAKE_CXXFLAGS += -std=c++11 + LIBS += -ltidys +} +!wasm: LIBS += \ + -ltidy \ + -lcurl +LIBS += \ + -licclient +!android: LIBS += -lqicclient +android { + QT += svg + PREFIX = $$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$$ANDROID_NDK_HOST/sysroot/usr + TRIPLE = aarch64-linux-android + contains(ANDROID_TARGET_ARCH,armeabi-v7a): TRIPLE = arm-linux-androideabi + contains(ANDROID_TARGET_ARCH,x86): TRIPLE = i686-linux-android + contains(ANDROID_TARGET_ARCH,x86_64): TRIPLE = x86_64-linux-android + LIBS += \ + -L$$PREFIX/lib/$$TRIPLE/$$API \ + -L$$QT_ANDROID_LIBDIR \ + -lqicclient_$$ANDROID_TARGET_ARCH + ANDROID_EXTRA_LIBS += \ + $$PREFIX/lib/$$TRIPLE/$$API/libcrypto_1_1.so \ + $$PREFIX/lib/$$TRIPLE/$$API/libssl_1_1.so \ + $$PREFIX/lib/$$TRIPLE/$$API/libcurl.so \ + $$PREFIX/lib/$$TRIPLE/$$API/libtidy.so \ + $$PREFIX/lib/$$TRIPLE/$$API/libicclient.so \ + $$QT_ANDROID_LIBDIR/libqicclient_$${ANDROID_TARGET_ARCH}.so + assets.path = /assets + assets.files = $$CA_BUNDLE + INSTALLS += assets +} diff --git a/namatoko.qrc b/namatoko.qrc new file mode 100644 index 0000000..bcb5510 --- /dev/null +++ b/namatoko.qrc @@ -0,0 +1,8 @@ +<RCC> + <qresource prefix="/"> + <file>qtquickcontrols2.conf</file> + <file>main.qml</file> + <file>images/onboarding-illustration-1.png</file> + <file>Catalog.qml</file> + </qresource> +</RCC> diff --git a/qicpos b/qicpos new file mode 160000 +Subproject fabfc8193acac84883dda0874abd777e888b112 diff --git a/qicpos.qrc b/qicpos.qrc new file mode 100644 index 0000000..b534680 --- /dev/null +++ b/qicpos.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>qicpos/ProductForm.ui.qml</file> + </qresource> +</RCC> diff --git a/qtquickcontrols2.conf b/qtquickcontrols2.conf new file mode 100644 index 0000000..0af35a2 --- /dev/null +++ b/qtquickcontrols2.conf @@ -0,0 +1,7 @@ +[Controls] +Style=Material + +[Material] +Theme=Light +Primary=#000000 +Background=#ffffff diff --git a/registration.c b/registration.c new file mode 100644 index 0000000..695cf53 --- /dev/null +++ b/registration.c @@ -0,0 +1,34 @@ +#ifdef __EMSCRIPTEN__ +#include <string.h> +#include <emscripten/fetch.h> +#else +#include <curl/curl.h> +#endif + +void sign_up(const char *brand, const char *certificate) +{ +#ifdef __EMSCRIPTEN__ + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + strcpy(attr.requestMethod, "POST"); + attr.requestData = brand; + attr.requestDataSize = strlen(brand); + emscripten_fetch(&attr, "register"); + (void)certificate; +#else + curl_global_init(CURL_GLOBAL_SSL); + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + if (certificate) + curl_easy_setopt(curl, CURLOPT_CAINFO, certificate); +#ifdef DEBUG + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); +#endif + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, brand); + curl_easy_setopt(curl, CURLOPT_URL, SAMPLEURL"/register"); + curl_easy_perform(curl); + curl_easy_cleanup(curl); + curl_global_cleanup(); +#endif +} |