QR code scanner (QML component) (#9464)

Co-authored-by: Richard Ramos <info@richardramos.me>
This commit is contained in:
Igor Sirotin 2023-03-08 05:08:38 +13:00 committed by GitHub
parent eed98809d1
commit 7c1c178d37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 293 additions and 16 deletions

3
.gitmodules vendored
View File

@ -116,3 +116,6 @@
[submodule "vendor/nim-taskpools"]
path = vendor/nim-taskpools
url = https://github.com/status-im/nim-taskpools.git
[submodule "ui/StatusQ/vendor/qzxing"]
path = ui/StatusQ/vendor/qzxing
url = https://github.com/status-im/qzxing.git

View File

@ -37,5 +37,7 @@
<integer>0</integer>
<key>NSHighResolutionCapable</key>
<string>True</string>
<key>NSCameraUsageDescription</key>
<string>Scan QR codes</string>
</dict>
</plist>

View File

@ -37,5 +37,7 @@
<integer>0</integer>
<key>NSHighResolutionCapable</key>
<string>True</string>
<key>NSCameraUsageDescription</key>
<string>Scan QR codes</string>
</dict>
</plist>

View File

@ -149,7 +149,7 @@ endif
MONITORING ?= false
ifneq ($(MONITORING), false)
DOTHERSIDE_CMAKE_PARAMS := ${DOTHERSIDE_CMAKE_PARAMS} -DMONITORING:BOOL=ON -DMONITORING_QML_ENTRY_POINT:STRING="/../monitoring/Main.qml"
DOTHERSIDE_CMAKE_PARAMS += -DMONITORING:BOOL=ON -DMONITORING_QML_ENTRY_POINT:STRING="/../monitoring/Main.qml"
endif
@ -175,18 +175,23 @@ ifneq ($(detected_OS),Windows)
NIM_PARAMS += --passL:"-L$(QT5_LIBDIR)"
endif
endif
# We manually link QZXing to Nim application,
# because static libraries are not compiled into other static libraries (DOtherSide).
QZXING := vendor/DOtherSide/build/qzxing/libqzxing.a
DOTHERSIDE := vendor/DOtherSide/build/lib/libDOtherSideStatic.a
DOTHERSIDE_CMAKE_PARAMS += -DENABLE_DYNAMIC_LIBS=OFF -DENABLE_STATIC_LIBS=ON
# order matters here, due to "-Wl,-as-needed"
NIM_PARAMS += --passL:"$(DOTHERSIDE)" --passL:"$(shell PKG_CONFIG_PATH="$(QT5_PCFILEDIR)" pkg-config --libs Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets Qt5Svg Qt5Multimedia)"
NIM_PARAMS += --passL:"$(DOTHERSIDE)" --passL:"$(QZXING)" --passL:"$(shell PKG_CONFIG_PATH="$(QT5_PCFILEDIR)" pkg-config --libs Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets Qt5Svg Qt5Multimedia)"
else
ifneq ($(QML_DEBUG), false)
QZXING := vendor/DOtherSide/build/qzxing/Debug/qzxing.lib
DOTHERSIDE := vendor/DOtherSide/build/lib/Debug/DOtherSide.dll
else
QZXING := vendor/DOtherSide/build/qzxing/Release/qzxing.lib
DOTHERSIDE := vendor/DOtherSide/build/lib/Release/DOtherSide.dll
endif
DOTHERSIDE_CMAKE_PARAMS += -T"v141" -A x64 -DENABLE_DYNAMIC_LIBS=ON -DENABLE_STATIC_LIBS=OFF
NIM_PARAMS += -L:$(DOTHERSIDE)
NIM_PARAMS += -L:$(DOTHERSIDE) -L:$(QZXING)
NIM_EXTRA_PARAMS := --passL:"-lsetupapi -lhid"
endif
@ -238,6 +243,9 @@ $(DOTHERSIDE): | deps
cmake $(DOTHERSIDE_CMAKE_PARAMS)\
-DENABLE_DOCS=OFF \
-DENABLE_TESTS=OFF \
-DQZXING_USE_QML=ON \
-DQZXING_MULTIMEDIA=ON \
-DQZXING_USE_DECODER_QR_CODE=ON \
.. $(HANDLE_OUTPUT) && \
$(DOTHERSIDE_BUILD_CMD)

View File

@ -13,5 +13,5 @@ docker run -it --rm \
-u jenkins:$(getent group $(whoami) | cut -d: -f3) \
-v "${PWD}:/status-desktop" \
-w /status-desktop \
statusteam/nim-status-client-build:1.2.0-qt5.15.2 \
statusteam/nim-status-client-build:1.2.1-qt5.15.2 \
./docker-linux-app-image.sh

View File

@ -40,10 +40,10 @@ RUN apt update -yq && apt install -yq software-properties-common \
&& add-apt-repository -y ppa:git-core/ppa \
&& add-apt-repository -y ppa:ubuntu-toolchain-r/test \
&& apt update -yq && apt full-upgrade -yq && apt install -yq --no-install-recommends --fix-missing \
gnupg2 openssh-client ca-certificates locales sudo jq curl wget fuse s3cmdfile llvm tk-dev xz-utils \
gnupg2 openssh-client ca-certificates locales sudo jq curl wget fuse s3cmd file llvm tk-dev xz-utils \
git make build-essential pkg-config cmake extra-cmake-modules gcc-9 g++-9 \
libgl1-mesa-dev libsm6 libice6 libfontconfig1 libdbus-1-3 libssl-dev libz-dev \
zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev unixodbc-dev libpq-dev \
libncurses5-dev libncursesw5-dev libpcsclite-dev libpcre3-dev libnss3 \
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-alsa libpulse-mainloop-glib0 \

View File

@ -7,7 +7,7 @@ pipeline {
agent {
docker {
label 'linux'
image 'statusteam/nim-status-client-build:1.2.0-qt5.15.2'
image 'statusteam/nim-status-client-build:1.2.1-qt5.15.2'
/* allows jenkins use cat and mounts '/dev/fuse' for linuxdeployqt */
args '--entrypoint="" --cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse'
}

View File

@ -7,7 +7,7 @@ pipeline {
agent {
docker {
label 'linux'
image 'statusteam/nim-status-client-build:1.2.0-qt5.15.2'
image 'statusteam/nim-status-client-build:1.2.1-qt5.15.2'
}
}

View File

@ -4,7 +4,7 @@
cp -R . ~/status-desktop
cd ~/status-desktop
git clean -dfx && rm -rf vendor/* && make -j4 V=1 update
git clean -dfx && rm -rf vendor/* && git checkout vendor/DOtherSide && make -j4 V=1 update
make V=1 pkg
# Make AppImage build accessible to the docker host

View File

@ -18,7 +18,7 @@ function check_version {
function install_build_dependencies {
echo "Install build dependencies"
apt update
apt install -yq git build-essential pkg-config mesa-common-dev \
apt install -yq git build-essential pkg-config mesa-common-dev unixodbc-dev libpq-dev \
libglu1-mesa-dev wget libpcsclite-dev libpcre3-dev libssl-dev libpulse-mainloop-glib0 \
libxkbcommon-x11-dev extra-cmake-modules cmake
}

View File

@ -13,12 +13,18 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(QZXING_USE_QML ON)
set(QZXING_MULTIMEDIA ON)
set(QZXING_USE_DECODER_QR_CODE ON)
# https://doc.qt.io/qtcreator/creator-qml-modules-with-plugins.html#importing-qml-modules
set(QML_IMPORT_PATH
${CMAKE_SOURCE_DIR}/src;${QML_IMPORT_PATH}
CACHE STRING "")
add_subdirectory(vendor/SortFilterProxyModel)
add_subdirectory(vendor/qzxing/src)
add_subdirectory(sandbox)
add_subdirectory(sanity_checker)
add_subdirectory(tests)

View File

@ -32,7 +32,8 @@ target_compile_definitions(${PROJECT_NAME}
PRIVATE SRC_DIR="${CMAKE_CURRENT_LIST_DIR}")
target_link_libraries(
${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Quick Qt5::QuickControls2
SortFilterProxyModel)
SortFilterProxyModel
qzxing)
if (APPLE)
find_library(AppKit AppKit)

View File

@ -1198,6 +1198,17 @@ QtObject {
hasNotification: false
notificationsCount: 0
}
ListElement {
sectionId: "demoApp"
sectionType: 103
name: "QR Scanner"
active: false
image: ""
icon: "qr-scan"
color: ""
hasNotification: false
notificationsCount: 0
}
}
property ListModel demoAppSectionsModel: ListModel {

View File

@ -5,6 +5,7 @@ import QtGraphicalEffects 1.13
import QtQuick.Layouts 1.14
import Qt.labs.settings 1.0
import QtQml.Models 2.14
import QtMultimedia 5.14
import StatusQ 0.1
@ -49,6 +50,7 @@ StatusWindow {
readonly property int apiDocumentation: 100
readonly property int examples: 101
readonly property int demoApp: 102
readonly property int qrScanner: 103
}
function setActiveItem(sectionId) {
@ -97,6 +99,10 @@ StatusWindow {
{
stackView.push(demoAppCmp)
}
else if (model.sectionType === appSectionType.qrScanner)
{
stackView.push(qrScannerComponent)
}
rootWindow.setActiveItem(model.sectionId)
}
}
@ -537,6 +543,50 @@ StatusWindow {
}
}
Component {
id: qrScannerComponent
Rectangle {
anchors.fill: parent
color: Theme.palette.baseColor3
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 10
StatusQrCodeScanner {
id: qrScanner
Layout.fillWidth: true
Layout.preferredHeight: width / sourceRatio
}
RowLayout {
Layout.fillWidth: true
spacing: 10
StatusBaseText {
text: qsTr("Last tag: ")
}
StatusInput {
id: lastTagText
Layout.fillWidth: true
input.enabled: false
text: qrScanner.lastTag
input.rightPadding: 10
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
}
StatusMacTrafficLights {
anchors.left: parent.left
anchors.top: parent.top

View File

@ -6,15 +6,17 @@
#include <QDirIterator>
#include "StatusQ/typesregistration.h"
#include <QZXing.h>
SandboxApp::SandboxApp(int &argc, char **argv)
: QGuiApplication(argc, argv)
{
QZXing::registerQMLTypes();
#ifdef QT_DEBUG
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, [this](const QString&) {
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, [this](const QString&) {
restartEngine();
});
#endif
}

View File

@ -23,7 +23,8 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${STATUSQ_DIR}/include)
target_link_libraries(
${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Quick Qt5::QuickControls2
SortFilterProxyModel)
SortFilterProxyModel
qzxing)
if (APPLE)
find_library(AppKit AppKit)

View File

@ -5,6 +5,7 @@
#include <QQmlEngine>
#include "StatusQ/typesregistration.h"
#include <QZXing>
int main(int argc, char *argv[])
{
@ -16,6 +17,8 @@ int main(int argc, char *argv[])
engine.addImportPath(QStringLiteral(":/"));
QZXing::registerQMLTypes();
QDirIterator it(":", QDirIterator::Subdirectories);
bool errorsFound = false;

View File

@ -0,0 +1,182 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtMultimedia 5.15
import QtGraphicalEffects 1.0
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import QZXing 3.3
Item {
id: root
readonly property alias camera: camera
readonly property size sourceSize: Qt.size(videoOutput.sourceRect.width, videoOutput.sourceRect.height)
readonly property size contentSize: Qt.size(videoOutput.contentRect.width, videoOutput.contentRect.height)
readonly property real sourceRatio: videoOutput.sourceRect.width / videoOutput.sourceRect.height
property int failsCount: 0
property int tagsCount: 0
property int decodeTime: 0
property string lastTag
implicitWidth: sourceSize.width
implicitHeight: sourceSize.height
signal tagFound(string tag)
QtObject {
id: d
readonly property int radius: 16
}
Camera {
id: camera
captureMode: Camera.CaptureVideo
focus {
focusMode: CameraFocus.FocusContinuous
focusPointMode: CameraFocus.FocusPointAuto
}
}
Rectangle {
anchors.fill: parent
color: Theme.palette.baseColor4
radius: d.radius
}
Item {
anchors.fill: parent
implicitWidth: videoOutput.contentRect.width
implicitHeight: videoOutput.contentRect.height
visible: camera.availability === Camera.Available
VideoOutput {
id: videoOutput
anchors.fill: parent
visible: false
source: camera
filters: [ qzxingFilter ]
fillMode: VideoOutput.PreserveAspectFit
}
Rectangle {
id: mask
anchors.fill: parent
radius: 16
visible: false
color: "black"
}
OpacityMask {
anchors.fill: parent
source: videoOutput
maskSource: mask
}
QZXingFilter {
id: qzxingFilter
orientation: videoOutput.orientation
captureRect: {
videoOutput.contentRect; videoOutput.sourceRect; // bindings
const normalizedRectangle = Qt.rect(0, 0, 1, 1)
const rectangle = videoOutput.mapNormalizedRectToItem(normalizedRectangle)
return videoOutput.mapRectToSource(rectangle);
}
decoder {
enabledDecoders: QZXing.DecoderFormat_QR_CODE
tryHarder: true
onTagFound: {
root.lastTag = tag
root.tagFound(tag)
}
}
onDecodingFinished: {
if (succeeded)
++root.tagsCount
else
++root.failsCount
root.decodeTime = decodeTime
}
}
}
// TODO: Implement camera selector
// For me it works once. The first switch between 2 cameras is ok.
// The second switch doesn't work and behaves different with 2 approaches:
// With standard `ComboBox` it throws an exception.
// Width `StatusComboBox` it just kinda unbinds from the Camera.
//
// ComboBox {
// id: cameraComboBox
// anchors {
// right: parent.right
// bottom: parent.bottom
// margins: 10
// }
// width: implicitWidth
// opacity: 0.7
// model: QtMultimedia.availableCameras
// textRole: "displayName"
// valueRole: "deviceId"
// onCurrentValueChanged: {
// camera.deviceId = currentValue
// }
// }
// StatusComboBox {
// id: cameraComboBox
// anchors {
// right: parent.right
// bottom: parent.bottom
// margins: 10
// }
// width: implicitWidth
// opacity: 0.7
// model: QtMultimedia.availableCameras
// control.textRole: "displayName"
// control.valueRole: "deviceId"
// onCurrentValueChanged: {
// console.log("setting deviceId to", currentValue)
// camera.deviceId = currentValue
// }
// }
ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
spacing: 10
StatusBaseText {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Theme.palette.dangerColor1
visible: camera.availability !== Camera.Available
text: qsTr("Camera is not available")
}
StatusBaseText {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Theme.palette.directColor5
visible: camera.errorCode !== Camera.NoError
text: "Error comes here" // camera.errorString
}
}
}

View File

@ -49,3 +49,4 @@ StatusChart 0.1 StatusChart.qml
StatusChartPanel 0.1 StatusChartPanel.qml
StatusStepper 0.1 StatusStepper.qml
LoadingComponent 0.1 LoadingComponent.qml
StatusQrCodeScanner 0.1 StatusQrCodeScanner.qml

View File

@ -196,5 +196,6 @@
<file>StatusQ/Core/Utils/ModelUtils.qml</file>
<file>StatusQ/Core/Utils/ModelsComparator.qml</file>
<file>StatusQ/Core/Utils/ModelChangeTracker.qml</file>
<file>StatusQ/Components/StatusQrCodeScanner.qml</file>
</qresource>
</RCC>

1
ui/StatusQ/vendor/qzxing vendored Submodule

@ -0,0 +1 @@
Subproject commit 80bb1d21903881e4061a41739d413a296ceb3b49

View File

@ -28,6 +28,7 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
endif()
endif()
add_subdirectory(../../ui/StatusQ/vendor/qzxing/src qzxing)
add_subdirectory(../../ui/StatusQ/vendor/SortFilterProxyModel SortFilterProxyModel)
add_subdirectory(lib)

View File

@ -38,7 +38,7 @@ macro(add_target name type)
target_include_directories(${name} PUBLIC include include/Qt ${STATUSQ_DIR}/include)
target_link_libraries(${name} PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Qml Qt5::Quick Qt5::Network Qt5::Multimedia SortFilterProxyModel)
target_link_libraries(${name} PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Qml Qt5::Quick Qt5::Network Qt5::Multimedia SortFilterProxyModel qzxing)
target_compile_definitions(${name} PRIVATE $<$<CONFIG:Debug>:QT_QML_DEBUG>)
if(DEFINED QML_DEBUG_PORT)
@ -53,7 +53,7 @@ macro(add_target name type)
endif()
# for DOtherSide.pc
set(PC_REQUIRES "Qt5Core, Qt5Gui, Qt5Widgets, Qt5Qml, Qt5Quick, Qt5Network, Qt5DBus, Qt5Multimedia, SortFilterProxyModel")
set(PC_REQUIRES "Qt5Core, Qt5Gui, Qt5Widgets, Qt5Qml, Qt5Quick, Qt5Network, Qt5DBus, Qt5Multimedia, SortFilterProxyModel, qzxing")
if (${Qt5QuickControls2_FOUND})
target_link_libraries(${name} PRIVATE Qt5::QuickControls2)
set(PC_REQUIRES "${PC_REQUIRES}, Qt5QuickControls2")

View File

@ -85,6 +85,7 @@
#endif
#include <qqmlsortfilterproxymodeltypes.h>
#include <QZXing.h>
namespace {
@ -103,6 +104,7 @@ void register_meta_types()
#endif
qqsfpm::registerTypes();
QZXing::registerQMLTypes();
}
}