Add per-app rules, transfer pairing, self-merchant flag, two-step onboarding
Notification parsing: - Per-app parse rules (parse_rules.packageName; getEnabledForApp, NULL = legacy global) - Transfer pairing: transfer_pair_matcher + transfer_pairing_blocklist table/dao/repo - source_apps.selfMerchant flag (Ozon inbox rework: default account picker, suppress AI category prefill + rule suggestion) - raw_messages.diagnostics dump captured under diagnostic-mode toggle - Inbox card / settings / log UI reworks Onboarding: - Two-step flow (name -> first account); UserSeeder seeds categories only, no accounts Schema bumped to v11; drop obsolete migration + mixed-merchant tests, add new coverage. Add ios/ platform folder. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="new_budget"
|
||||
android:label="Kitty finances"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
|
||||
+21
-1
@@ -27,11 +27,25 @@ class NotificationIngestService : NotificationListenerService() {
|
||||
val extras = notification.extras ?: return
|
||||
val title = extras.getCharSequence(Notification.EXTRA_TITLE)?.toString()
|
||||
// big-text полнее обычного text — берём его при наличии.
|
||||
val body = (extras.getCharSequence(Notification.EXTRA_BIG_TEXT)
|
||||
val extrasBody = (extras.getCharSequence(Notification.EXTRA_BIG_TEXT)
|
||||
?: extras.getCharSequence(Notification.EXTRA_TEXT))
|
||||
?.toString()
|
||||
?.trim()
|
||||
.orEmpty()
|
||||
// ВТБ прячет текст операции: в EXTRA_TEXT кладёт заглушку
|
||||
// «Конфиденциальная информация в уведомлении скрыта» (реальный контент
|
||||
// рисуется в custom RemoteViews и в extras не попадает), а полный текст
|
||||
// («Списание 2000р Счет*3891 …») доступен только в tickerText. Второго
|
||||
// поста с текстом, как у Т-Банка, ВТБ не шлёт — поэтому при пустом body
|
||||
// или заглушке падаем обратно на ticker.
|
||||
val ticker = notification.tickerText?.toString()?.trim().orEmpty()
|
||||
val body = if ((extrasBody.isEmpty() || isHiddenContentStub(extrasBody)) &&
|
||||
ticker.isNotEmpty()
|
||||
) {
|
||||
ticker
|
||||
} else {
|
||||
extrasBody
|
||||
}
|
||||
|
||||
val diagnosticMode = store.isDiagnosticMode()
|
||||
|
||||
@@ -51,6 +65,12 @@ class NotificationIngestService : NotificationListenerService() {
|
||||
LiveSink.tick()
|
||||
}
|
||||
|
||||
/** Заглушка «содержимое скрыто» вместо текста операции (ВТБ и т.п.). */
|
||||
private fun isHiddenContentStub(body: String): Boolean {
|
||||
val lower = body.lowercase()
|
||||
return lower.contains("конфиденциальн") && lower.contains("скрыт")
|
||||
}
|
||||
|
||||
/**
|
||||
* Человекочитаемый дамп уведомления для режима диагностики: `key`,
|
||||
* `postTime`, `visibility`, `flags`, `tickerText` и все поля `extras`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="notif_listener_label">NewBudget — распознавание уведомлений</string>
|
||||
<string name="notif_listener_label">Kitty finances — распознавание уведомлений</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user