1
0
forked from BRT/arc

Force portrait orientation on connected devices, remove x100 heuristic from transaction matching, and add secondary text search filter in monitoring app.

This commit is contained in:
slava 2026-06-08 21:06:00 +05:00
parent e8ddde7e36
commit 2c2d58021f
5 changed files with 66 additions and 13 deletions

View File

@ -816,6 +816,33 @@ QString AdbUtils::getForegroundPackage(const QString &deviceId) {
return {};
}
bool AdbUtils::ensurePortraitOrientation(const QString &deviceId) {
if (deviceId.isEmpty()) {
return false;
}
const QString adb = adbPath();
QString out;
if (!startProcess(adb, {"-s", deviceId, "shell", "dumpsys", "input"}, &out)) {
return false; // adb недоступен — пропускаем (best-effort)
}
// SurfaceOrientation: 0=0°(portrait) 1=90°(landscape) 2=180° 3=270°(landscape)
static const QRegularExpression re(R"(SurfaceOrientation:\s*(\d))");
const QRegularExpressionMatch m = re.match(out);
const int orient = m.hasMatch() ? m.captured(1).toInt() : 0;
if (orient == 1 || orient == 3) {
qInfo() << "[AdbUtils] device" << deviceId
<< "in landscape (orient" << orient << ") → forcing portrait";
// Автоповорот выключаем, иначе сенсор вернёт landscape.
startProcess(adb, {"-s", deviceId, "shell", "settings", "put",
"system", "accelerometer_rotation", "0"});
startProcess(adb, {"-s", deviceId, "shell", "settings", "put",
"system", "user_rotation", "0"});
}
return true;
}
void AdbUtils::unlockScreen(const QString &deviceId, const int screenWidth, const int screenHeight) {
const QString adb = adbPath();

View File

@ -62,6 +62,13 @@ public:
// Возвращает пакет приложения на переднем плане (foreground), либо пустую строку при ошибке
static QString getForegroundPackage(const QString &deviceId);
// Проверяет реальную ориентацию экрана (dumpsys input → SurfaceOrientation).
// Если экран в горизонтальной ориентации (landscape, 90°/270°), принудительно
// ставит вертикальную: выключает автоповорот и выставляет user_rotation=0.
// Best-effort: при ошибке adb просто ничего не делает. Возвращает true, если
// экран в портрете (изначально или после форсирования).
static bool ensurePortraitOrientation(const QString &deviceId);
private:
static QString adbDir();
static bool startProcess(const QString &program, const QStringList &args,

View File

@ -719,14 +719,16 @@ void GetLastTransactionsScript::searchAll(
const double absListAmt = qAbs(tx.amount);
// Выбираем подходящую цель: совпадение по сумме + пред-фильтры даты/времени.
// Список Выписки, экран деталей и поисковый таргет — все в мажорных
// единицах (TJS, 2 знака), поэтому сверяем сумму напрямую. Эвристик
// "x100" убран: он ложно матчил реальную транзакцию на 1.00 к поиску
// на 100.00 (1.00*100==100.00) и затем домножал результат до 10000.
int matchIdx = -1;
bool matchedX100 = false;
for (int ti = 0; ti < pending.size(); ++ti) {
const double searchAbs = qAbs(pending[ti].first);
const QDateTime &cand = pending[ti].second;
const bool amountDirect = qAbs(absListAmt - searchAbs) < 0.01;
const bool amountX100 = qAbs(absListAmt * 100.0 - searchAbs) < 0.01;
if (!amountDirect && !amountX100) continue;
if (!amountDirect) continue;
// Pre-filter по ДАТЕ: только если дата надёжная (из content-desc P2P формата).
// DCWallet транзакции получают дату из groupDates ImageView, которая
@ -753,14 +755,12 @@ void GetLastTransactionsScript::searchAll(
}
matchIdx = ti;
matchedX100 = amountX100 && !amountDirect;
break;
}
if (matchIdx < 0) continue;
const QDateTime searchTime = pending[matchIdx].second;
qDebug() << "[Dushanbe::GetLastTransactions] Amount match:" << tx.amount
<< (matchedX100 ? "(scaled x100)" : "(direct)")
<< "date:" << tx.date << "time:" << tx.time
<< "→ target idx" << matchIdx;
@ -947,8 +947,8 @@ void GetLastTransactionsScript::searchAll(
// Формируем task_result напрямую из распарсенных данных и отдаём
// EventHandler через сигнал — без записи/чтения БД.
{
const double finalAmount = matchedX100 ? detail.amount * 100.0 : detail.amount;
const double finalFee = matchedX100 ? detail.fee * 100.0 : detail.fee;
const double finalAmount = detail.amount;
const double finalFee = detail.fee;
QDateTime txTime = QDateTime::fromString(
detail.date + " " + detail.time, "dd.MM.yyyy HH:mm:ss");

View File

@ -982,7 +982,7 @@ def admin_logout() -> Response:
return resp
def _query_entries(kind, type_, event, q, date_from, date_to, page):
def _query_entries(kind, type_, event, q, date_from, date_to, page, q2=""):
where, params = [], []
if kind:
where.append("kind=?"); params.append(kind)
@ -992,6 +992,8 @@ def _query_entries(kind, type_, event, q, date_from, date_to, page):
where.append("event LIKE ?"); params.append(f"%{event}%")
if q:
where.append("message LIKE ?"); params.append(f"%{q}%")
if q2:
where.append("message LIKE ?"); params.append(f"%{q2}%")
df = _parse_date(date_from)
if df is not None:
where.append("ts>=?"); params.append(df)
@ -1096,6 +1098,7 @@ def admin_dashboard(
type: str = "",
event: str = "",
q: str = "",
q2: str = "",
date_from: str = "",
date_to: str = "",
page: int = 0,
@ -1103,11 +1106,11 @@ def admin_dashboard(
if not _is_authed(request):
return RedirectResponse("/admin/login", status_code=303)
rows, total, types = _query_entries(kind, type, event, q, date_from, date_to, page)
rows, total, types = _query_entries(kind, type, event, q, date_from, date_to, page, q2)
page = max(0, page)
current_url = (f"/admin?kind={kind}&type={html_lib.escape(type)}&event={html_lib.escape(event)}"
f"&q={html_lib.escape(q)}&date_from={date_from}&date_to={date_to}&page={page}")
f"&q={html_lib.escape(q)}&q2={html_lib.escape(q2)}&date_from={date_from}&date_to={date_to}&page={page}")
trs = _build_entry_trs(rows, current_url)
@ -1116,7 +1119,7 @@ def admin_dashboard(
def link(p: int) -> str:
qs = (f"kind={kind}&type={html_lib.escape(type)}&event={html_lib.escape(event)}"
f"&q={html_lib.escape(q)}&date_from={date_from}&date_to={date_to}&page={p}")
f"&q={html_lib.escape(q)}&q2={html_lib.escape(q2)}&date_from={date_from}&date_to={date_to}&page={p}")
return f"/admin?{qs}"
shown_from = page * PAGE_SIZE + 1 if total else 0
@ -1133,6 +1136,7 @@ def admin_dashboard(
<label>Тип<select name=type>{type_opts}</select></label>
<label>Event<input name=event value="{html_lib.escape(event)}" placeholder="task_error…"></label>
<label>Поиск в тексте<input name=q value="{html_lib.escape(q)}" placeholder="подстрока"></label>
<label>И ещё<input name=q2 value="{html_lib.escape(q2)}" placeholder="вторая подстрока"></label>
<label>С (UTC)<input name=date_from type=datetime-local step=1 value="{html_lib.escape(date_from)}"></label>
<label>По (UTC)<input name=date_to type=datetime-local step=1 value="{html_lib.escape(date_to)}"></label>
<button type=submit>Фильтр</button>
@ -1219,6 +1223,7 @@ def admin_entries_fragment(
type: str = "",
event: str = "",
q: str = "",
q2: str = "",
date_from: str = "",
date_to: str = "",
page: int = 0,
@ -1227,8 +1232,8 @@ def admin_entries_fragment(
raise HTTPException(status_code=401, detail="auth required")
page = max(0, page)
current_url = (f"/admin?kind={kind}&type={html_lib.escape(type)}&event={html_lib.escape(event)}"
f"&q={html_lib.escape(q)}&date_from={date_from}&date_to={date_to}&page={page}")
rows, total, _ = _query_entries(kind, type, event, q, date_from, date_to, page)
f"&q={html_lib.escape(q)}&q2={html_lib.escape(q2)}&date_from={date_from}&date_to={date_to}&page={page}")
rows, total, _ = _query_entries(kind, type, event, q, date_from, date_to, page, q2)
trs = _build_entry_trs(rows, current_url)
return JSONResponse({"rows_html": "".join(trs), "total": total})

View File

@ -719,6 +719,20 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn
const auto thr = new QThread(this);
const QString key = account.deviceId;
// Перед запуском любой задачи гарантируем вертикальную ориентацию экрана:
// если телефон в landscape — принудительно ставим portrait. Выполняется в
// рабочем потоке (functor без context → исполняется в потоке-эмиттере thr),
// поэтому adb-I/O не блокирует поток диспетчера. Коннект добавлен до setup(),
// значит сработает раньше, чем worker::start → doStart().
const QString canonicalDeviceId = account.deviceId;
connect(thr, &QThread::started, [canonicalDeviceId]() {
DeviceInfo dev = DeviceDAO::getDeviceById(canonicalDeviceId);
if (dev.id.isEmpty()) dev = DeviceDAO::findByAdbSerial(canonicalDeviceId);
const QString serial = dev.adbSerial.isEmpty() ? canonicalDeviceId
: dev.adbSerial;
AdbUtils::ensurePortraitOrientation(serial);
});
// Generic helper: подключает сигналы и переносит worker в поток
auto setup = [&](auto *worker) {
using W = std::remove_pointer_t<decltype(worker)>;