diff --git a/apps/readest-sync/qml/DecisionDialog.qml b/apps/readest-sync/qml/DecisionDialog.qml
index 041108b..6dcb871 100644
--- a/apps/readest-sync/qml/DecisionDialog.qml
+++ b/apps/readest-sync/qml/DecisionDialog.qml
@@ -1,13 +1,12 @@
import QtQuick
-Item {
+ModalSurface {
id: root
objectName: "nativeDecisionDialog"
required property var shell
z: 60
readonly property var choices: shell.view.actionPresentation ? shell.view.actionPresentation.choices : []
- Rectangle { anchors.fill: parent; color: "#dddddd"; opacity: 0.92 }
Rectangle {
anchors.centerIn: parent
width: Math.min(parent.width - 2 * shell.metrics.largeGap, 1200 * shell.metrics.scale)
@@ -33,6 +32,7 @@ Item {
model: root.choices
delegate: Rectangle {
required property var modelData
+ objectName: "nativeDecisionChoice_" + modelData.command
width: dialogColumn.width
height: shell.metrics.primaryAction
color: choiceTap.pressed ? "black" : "white"
@@ -50,6 +50,7 @@ Item {
}
}
Rectangle {
+ objectName: "nativeDecisionDismiss"
width: parent.width
height: shell.metrics.primaryAction
color: finalTap.pressed ? "black" : "white"
diff --git a/apps/readest-sync/qml/ModalSurface.qml b/apps/readest-sync/qml/ModalSurface.qml
new file mode 100644
index 0000000..6dd22ce
--- /dev/null
+++ b/apps/readest-sync/qml/ModalSurface.qml
@@ -0,0 +1,12 @@
+import QtQuick
+
+Item {
+ Rectangle { anchors.fill: parent; z: -2; color: "#dddddd"; opacity: 0.92 }
+ // Content remains above the input shield; background taps never reach pages.
+ MouseArea {
+ anchors.fill: parent
+ z: -1
+ acceptedButtons: Qt.AllButtons
+ onWheel: function(wheel) { wheel.accepted = true }
+ }
+}
diff --git a/apps/readest-sync/qml/OperationSurface.qml b/apps/readest-sync/qml/OperationSurface.qml
index 042e355..db65f8f 100644
--- a/apps/readest-sync/qml/OperationSurface.qml
+++ b/apps/readest-sync/qml/OperationSurface.qml
@@ -1,11 +1,10 @@
import QtQuick
-Item {
+ModalSurface {
id: root
objectName: "nativeOperationSurface"
required property var shell
z: 50
- Rectangle { anchors.fill: parent; color: "#dddddd"; opacity: 0.92 }
Rectangle {
anchors.centerIn: parent
width: Math.min(parent.width - 2 * shell.metrics.largeGap, 1200 * shell.metrics.scale)
@@ -27,6 +26,7 @@ Item {
wrapMode: Text.Wrap
}
Rectangle {
+ objectName: "nativeOperationCancel"
width: parent.width
height: shell.metrics.primaryAction
color: cancelTap.pressed ? "black" : "white"
diff --git a/apps/readest-sync/qml/readest.qrc b/apps/readest-sync/qml/readest.qrc
index a316072..25a1d83 100644
--- a/apps/readest-sync/qml/readest.qrc
+++ b/apps/readest-sync/qml/readest.qrc
@@ -1 +1 @@
-Main.qmlNativeMetrics.qmlNativeHeader.qmlMessageBand.qmlOperationSurface.qmlDecisionDialog.qmlBusyPage.qmlErrorPage.qmlSignInPage.qmlLibraryPage.qmlBookDetailsPage.qmlActionButton.qmlBodyText.qml
+Main.qmlNativeMetrics.qmlNativeHeader.qmlMessageBand.qmlModalSurface.qmlOperationSurface.qmlDecisionDialog.qmlBusyPage.qmlErrorPage.qmlSignInPage.qmlLibraryPage.qmlBookDetailsPage.qmlActionButton.qmlBodyText.qml
diff --git a/apps/readest-sync/tests/qt_ui_test.cpp b/apps/readest-sync/tests/qt_ui_test.cpp
index 4aa9e32..c8a4d35 100644
--- a/apps/readest-sync/tests/qt_ui_test.cpp
+++ b/apps/readest-sync/tests/qt_ui_test.cpp
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -43,14 +44,18 @@ static QQuickItem* find_item(QQuickItem* item,const QString& name) {
for(auto* child:item->childItems()) if(auto* found=find_item(child,name)) return found;
return nullptr;
}
-static void tap_book(QQuickWindow* window) {
- auto* row=find_item(window->contentItem(),"detailedBookRow"); assert(row);
- const auto point=row->mapToScene(QPointF(row->width()/2,row->height()/2));
+static void tap_item(QQuickWindow* window,QQuickItem* item,int hold=0) {
+ assert(item);
+ const auto point=item->mapToScene(QPointF(item->width()/2,item->height()/2));
QMouseEvent press(QEvent::MouseButtonPress,point,point,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
QCoreApplication::sendEvent(window,&press);
+ if(hold) settle(hold);
QMouseEvent release(QEvent::MouseButtonRelease,point,point,Qt::LeftButton,Qt::NoButton,Qt::NoModifier);
QCoreApplication::sendEvent(window,&release); settle();
}
+static void tap_book(QQuickWindow* window,int hold=0) {
+ tap_item(window,find_item(window->contentItem(),"detailedBookRow"),hold);
+}
static ApplicationConfig configAt(const QString& base) {
ApplicationConfig config;
config.root=(base+"/system/readest-sync").toStdString();
@@ -552,6 +557,8 @@ int main(int argc,char** argv) {
QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Menu))); settle();
assert(!libraryPage->property("menuOpen").toBool());
hold_foreground=true; control.refreshLibrary(); settle(); assert(control.busy());
+ tap_book(window,QGuiApplication::styleHints()->mousePressAndHoldInterval()+100);
+ assert(!libraryPage->property("contextOpen").toBool());
tap_book(window); assert(!control.detail());
QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Menu))); settle();
assert(!libraryPage->property("menuOpen").toBool());
@@ -570,6 +577,12 @@ int main(int argc,char** argv) {
auto view=menuView;
if(!guard.isEmpty()) view[guard]=true;
window->setProperty("view",view); settle();
+ if(!guard.isEmpty()) {
+ tap_book(window,QGuiApplication::styleHints()->mousePressAndHoldInterval()+100);
+ assert(!libraryPage->property("contextOpen").toBool());
+ tap_book(window);
+ assert(!libraryPage->property("activating").toBool());
+ }
for(bool hardware:{false,true}) {
libraryPage->setProperty("menuOpen",false);
if(hardware) QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Menu)));
@@ -578,6 +591,33 @@ int main(int argc,char** argv) {
}
}
window->setProperty("view",QVariant::fromValue(&control)); settle();
+ // Dialog controls must still receive real taps above the shared shield.
+ for(const auto& mode:{QString("busy"),QString("decision"),QString("blocking")}) {
+ auto view=engine.toScriptValue(menuView);
+ view.setProperty(mode,true);
+ view.setProperty("lastCommand",QString());
+ view.setProperty("close",engine.evaluate("(function() { this.lastCommand = 'close'; })"));
+ view.setProperty("cancelDecision",engine.evaluate("(function() { this.lastCommand = 'cancel'; })"));
+ view.setProperty("acknowledgeBlocking",engine.evaluate("(function() { this.lastCommand = 'acknowledge'; })"));
+ view.setProperty("runAction",engine.evaluate("(function(command) { this.lastCommand = command; })"));
+ view.setProperty("actionPresentation",engine.toScriptValue(QVariantMap{{"choices",QVariantList{
+ QVariantMap{{"command","usePocketBook"},{"text","Use PocketBook position"}},
+ QVariantMap{{"command","useReadest"},{"text","Use Readest position"}}}}}));
+ window->setProperty("view",QVariant::fromValue(view)); settle();
+ if(mode=="decision") {
+ for(const auto& command:{QString("usePocketBook"),QString("useReadest")}) {
+ tap_item(window,find_item(window->contentItem(),"nativeDecisionChoice_"+command));
+ assert(view.property("lastCommand").toString()==command);
+ }
+ }
+ tap_item(window,find_item(window->contentItem(),mode=="busy"?"nativeOperationCancel":"nativeDecisionDismiss"));
+ assert(view.property("lastCommand").toString()==(mode=="busy"?"close":mode=="decision"?"cancel":"acknowledge"));
+ }
+ window->setProperty("view",QVariant::fromValue(&control)); settle();
+ tap_book(window,QGuiApplication::styleHints()->mousePressAndHoldInterval()+100);
+ assert(libraryPage->property("contextOpen").toBool());
+ QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Back))); settle();
+ assert(!libraryPage->property("contextOpen").toBool());
libraryPage->setProperty("menuOpen",true); settle();
assert(window->findChild("nativeLibraryMenu")->isVisible());
QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Back))); settle();