diff --git a/android/ozon/GetLastTransactionsScript.cpp b/android/ozon/GetLastTransactionsScript.cpp index 109c9b9..6ac50c6 100644 --- a/android/ozon/GetLastTransactionsScript.cpp +++ b/android/ozon/GetLastTransactionsScript.cpp @@ -379,37 +379,53 @@ void GetLastTransactionsScript::searchTransaction(const DeviceInfo &device, int // Хелпер: находит Y-диапазон [dateY, nextDateY) для нужной даты на экране. // Кнопки транзакций между этими Y принадлежат нужному дню. - // Если заголовок нужной даты ушёл за верх экрана, но следующая дата ещё - // видна ниже (или не видна вовсе), dateY = 0 — все кнопки выше nextDateY - // принадлежат нашему дню. + // Возвращает специальные коды через dateY: + // dateY >= 0 : header виден на этом Y (нормальный случай) + // dateY == 0 : header выше viewport, есть более старая видимая дата + // dateY == -1 : header не виден / лежит в нижней sticky-пачке + // (скролл вниз, чтобы target выехал в видимую часть) + // dateY == -2 : header в верхней sticky-пачке (несколько дат свёрнуто + // над viewport) — скролл вверх, чтобы пачка распалась auto findDateYRange = [&]() -> QPair { const QMap yCount = computeDateYCount(); - auto isVisible = [&](int y) { return y > 0 && yCount.value(y, 0) == 1; }; + auto isAnchored = [&](int y) { return y > 0 && yCount.value(y, 0) == 1; }; + auto isPiled = [&](int y) { return y > 0 && yCount.value(y, 0) > 1; }; + const int viewportMid = device.screenHeight / 2; int dateY = -1; int nextDateY = device.screenHeight; + int targetPileY = -1; bool foundOlderDate = false; for (const Node &node : xmlScreenParser.getNodes()) { if (node.className != "android.widget.TextView") continue; const QDate d = parseDateHeader(node.text.trimmed()); if (!d.isValid()) continue; if (d == searchDate) { - if (isVisible(node.y())) dateY = node.y(); + if (isAnchored(node.y())) dateY = node.y(); + else if (isPiled(node.y())) targetPileY = node.y(); } else if (d < searchDate) { foundOlderDate = true; - if (isVisible(node.y()) && (dateY < 0 || node.y() > dateY) + if (isAnchored(node.y()) + && (dateY < 0 || node.y() > dateY) && node.y() < nextDateY) { nextDateY = node.y(); } } } + // Target в sticky-пачке: пачка над viewport (малый Y) → секция уже + // прокручена выше, надо вернуться (dateY = -2). Пачка снизу viewport + // (большой Y) → target ещё не доехал до экрана, скролл вниз (dateY = -1). + if (dateY < 0 && targetPileY >= 0) { + dateY = (targetPileY < viewportMid) ? -2 : -1; + } // Target-header не виден, но видна более старая дата → target ушёл вверх: // всё выше nextDateY принадлежит нашему дню. - if (dateY < 0 && nextDateY < device.screenHeight) { + if (dateY < 0 && nextDateY < device.screenHeight && targetPileY < 0) { dateY = 0; } qDebug() << "[FetchTransactions] findDateYRange: dateY=" << dateY - << "nextDateY=" << nextDateY << "foundOlderDate=" << foundOlderDate; + << "nextDateY=" << nextDateY << "foundOlderDate=" << foundOlderDate + << "targetPileY=" << targetPileY; return {dateY, nextDateY}; }; @@ -440,6 +456,19 @@ void GetLastTransactionsScript::searchTransaction(const DeviceInfo &device, int // Находим Y-диапазон заголовка нужной даты const auto [dateHeaderY, nextDateHeaderY] = findDateYRange(); + if (dateHeaderY == -2) { + // Target застрял в sticky-пачке (Ozon стекает заголовки выше viewport + // в одну Y-позицию). Ниже пачки есть anchored более старая дата → + // секцию target уже проскроллили мимо. Скроллим обратно вверх, + // чтобы target распался из пачки и занял реальный Y. + qDebug() << "[FetchTransactions] Target piled in sticky band — scrolling up to detach"; + AdbUtils::makeSwipe(m_deviceId, device.screenWidth / 2, + device.screenHeight / 3, + device.screenWidth / 2, + device.screenHeight * 2 / 3, 600); + QThread::msleep(1500); + continue; + } if (dateHeaderY < 0) { // Target-header не виден и ни одной более старой даты не видно → // target скорее всего ниже вьюпорта. Скроллим вниз (swipe вверх). diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index c534a44..8081291 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -514,7 +514,18 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn const QJsonObject taskJson = QJsonDocument::fromJson(event.json.toUtf8()).object(); const QJsonObject body = taskJson["body"].toObject(); const double searchAmount = body["amount"].toDouble() / 100.0; // копейки → рубли (с учётом знака) - const QDateTime searchTime = QDateTime::fromString(body["created_at"].toString(), Qt::ISODate); + const QString createdAtStr = body["created_at"].toString(); + QDateTime searchTime = QDateTime::fromString(createdAtStr, Qt::ISODate); + // Backend контракт — UTC ISO с суффиксом 'Z' (как мы сами пишем + // на строках 317 и 373). Если 'Z'/смещение отсутствует — Qt парсит + // как LocalTime, что даёт off-by-one для вечерних транзакций + // (см. task_dump (26): Ozon группировал +100 на 14.05, backend + // прислал 13.05 без TZ). Жёстко переинтерпретируем как UTC. + if (searchTime.isValid() && searchTime.timeSpec() == Qt::LocalTime) { + qWarning() << "[EventHandler] FETCH_TRANSACTIONS: created_at без TZ-маркера" + << createdAtStr << "— интерпретирую как UTC"; + searchTime.setTimeSpec(Qt::UTC); + } if (appCode == "ozon") { auto *w = new Ozon::GetLastTransactionsScript(adbSerial, appCode, 0, searchAmount, searchTime, account.materialId); diff --git a/views/device/DeviceWidget.cpp b/views/device/DeviceWidget.cpp index 8ac7d0d..6fa182b 100644 --- a/views/device/DeviceWidget.cpp +++ b/views/device/DeviceWidget.cpp @@ -626,7 +626,11 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p const bool isOffline = device.status != "device"; - QList apps = BankProfileDAO::getApplicationsByDeviceId(device.id); + // Пока устройство не зарегистрировано на сервере (нет apiId) — банки не показываем + QList apps; + if (!device.apiId.isEmpty()) { + apps = BankProfileDAO::getApplicationsByDeviceId(device.id); + } // Фильтр по стране профиля const QString profileCountry = SettingsDAO::get("country");