From f8dbe8e0f771949f83e91b5e37a690e571a047ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=A6=8C=EA=A6=AB=EA=A6=B6=EA=A6=8F=EA=A7=80=EA=A6=A6?= =?UTF-8?q?=EA=A6=BF=EA=A6=A7=EA=A6=AE=EA=A6=91=EA=A6=A9=EA=A6=AD=EA=A7=80?= Date: Sun, 4 Jul 2021 15:29:04 +0800 Subject: Separated client code --- .gitignore | 4 +++ .gitmodules | 6 ++++ Catalog.qml | 28 ++++++++++++++++ README.md | 48 +++++++++++++++++++++++++++ controller.cxx | 55 +++++++++++++++++++++++++++++++ controller.hxx | 21 ++++++++++++ handler.c | 62 +++++++++++++++++++++++++++++++++++ images/onboarding-illustration-1.png | Bin 0 -> 64816 bytes larva | 1 + larva.qrc | 5 +++ main.cxx | 12 +++++++ main.qml | 46 ++++++++++++++++++++++++++ namatoko.pro | 50 ++++++++++++++++++++++++++++ namatoko.qrc | 8 +++++ qicpos | 1 + qicpos.qrc | 5 +++ qtquickcontrols2.conf | 7 ++++ registration.c | 34 +++++++++++++++++++ 18 files changed, 393 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Catalog.qml create mode 100644 README.md create mode 100644 controller.cxx create mode 100644 controller.hxx create mode 100644 handler.c create mode 100644 images/onboarding-illustration-1.png create mode 160000 larva create mode 100644 larva.qrc create mode 100644 main.cxx create mode 100644 main.qml create mode 100644 namatoko.pro create mode 100644 namatoko.qrc create mode 160000 qicpos create mode 100644 qicpos.qrc create mode 100644 qtquickcontrols2.conf create mode 100644 registration.c 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 +#include +#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(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 + +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 +#include +#include +#include + +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 new file mode 100644 index 0000000..c09f062 Binary files /dev/null and b/images/onboarding-illustration-1.png differ diff --git a/larva b/larva new file mode 160000 index 0000000..4f7f7f1 --- /dev/null +++ b/larva @@ -0,0 +1 @@ +Subproject commit 4f7f7f1a79ea43aeb2266fca27d27346a78125a1 diff --git a/larva.qrc b/larva.qrc new file mode 100644 index 0000000..61307a5 --- /dev/null +++ b/larva.qrc @@ -0,0 +1,5 @@ + + + larva/features/EmailForm.ui.qml + + 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 +#include +#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 @@ + + + qtquickcontrols2.conf + main.qml + images/onboarding-illustration-1.png + Catalog.qml + + diff --git a/qicpos b/qicpos new file mode 160000 index 0000000..fabfc81 --- /dev/null +++ b/qicpos @@ -0,0 +1 @@ +Subproject commit fabfc8193acac84883dda0874abd777e888b112e diff --git a/qicpos.qrc b/qicpos.qrc new file mode 100644 index 0000000..b534680 --- /dev/null +++ b/qicpos.qrc @@ -0,0 +1,5 @@ + + + qicpos/ProductForm.ui.qml + + 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 +#include +#else +#include +#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 +} -- cgit v1.2.3