app: don't auto-sync offline recordings for custom STT users; manual sync with confirmation#7691
Conversation
|
@greptile-apps review |
Greptile SummaryThis PR disables automatic offline-recording sync for users with a custom STT provider and adds an explicit confirmation dialog for manual syncs, preventing those recordings from being silently transcribed on Omi's servers and counting against the plan limit.
Confidence Score: 4/5Safe to merge for the auto-sync paths; the error-card retry pills in sync_page.dart and auto_sync_page.dart remain ungated and can silently consume transcription quota for custom-STT users who experience a sync failure. All four auto-sync entry points are correctly blocked, and the three primary manual-sync actions are properly gated behind the confirmation dialog. The remaining gap is the unguarded retrySync() call that surfaces after a failed sync — a custom-STT user who sees the error card can tap Retry and upload to Omi's servers without ever seeing the warning dialog, which is the exact behaviour this PR is trying to prevent. app/lib/pages/conversations/sync_page.dart and app/lib/pages/conversations/auto_sync_page.dart — the error-card retry pills call retrySync() directly without going through confirmSyncForCustomStt. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Offline recording created] --> B{Sync trigger}
B -->|Auto: session end / startup / device reconnect / phone file timer| C{useCustomStt?}
B -->|Manual: tap Sync button| D{useCustomStt?}
C -->|Yes| E[Skip — no auto-sync\ncapture_provider / sync_provider / home/page]
C -->|No| F[Auto-sync via syncWals]
D -->|Yes| G[confirmSyncForCustomStt\nshow OmiConfirmDialog]
D -->|No| H[Proceed directly to syncWals]
G -->|Cancel / dismiss| I[Abort — nothing synced]
G -->|Confirm| J{context.mounted?}
J -->|Yes| K[syncWals — Omi STT\ncounts toward limit]
J -->|No| L[Abort — widget disposed]
H --> K
M[Error pill: retrySync] -->|Ungated — no dialog| K
style E fill:#c8e6c9
style I fill:#c8e6c9
style L fill:#c8e6c9
style K fill:#ffe0b2
style M fill:#ffcdd2
Reviews (2): Last reviewed commit: "app: guard context.mounted after sync co..." | Re-trigger Greptile |
| action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async { | ||
| if (await confirmSyncForCustomStt(context)) p.syncWals(); | ||
| }); | ||
| } else if (readyToBackUp > 0) { | ||
| title = l.syncCardReadyCount(readyToBackUp); | ||
| action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () => p.syncWals()); | ||
| action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async { | ||
| if (await confirmSyncForCustomStt(context)) p.syncWals(); | ||
| }); |
There was a problem hiding this comment.
Missing
context.mounted guard after the await in the status pill callbacks. Consistent with the pattern used in sync_page.dart (_handleSyncWals), a mounted check should follow every await before any subsequent work, even when that work doesn't directly use context. This is also needed to satisfy the use_build_context_synchronously lint if it is enabled.
| action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async { | |
| if (await confirmSyncForCustomStt(context)) p.syncWals(); | |
| }); | |
| } else if (readyToBackUp > 0) { | |
| title = l.syncCardReadyCount(readyToBackUp); | |
| action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () => p.syncWals()); | |
| action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async { | |
| if (await confirmSyncForCustomStt(context)) p.syncWals(); | |
| }); | |
| action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async { | |
| if (await confirmSyncForCustomStt(context) && context.mounted) p.syncWals(); | |
| }); | |
| } else if (readyToBackUp > 0) { | |
| title = l.syncCardReadyCount(readyToBackUp); | |
| action = _statusActionPill(l.sync, Colors.deepPurpleAccent, () async { | |
| if (await confirmSyncForCustomStt(context) && context.mounted) p.syncWals(); | |
| }); |
| onPressed: () async { | ||
| if (await confirmSyncForCustomStt(context)) syncProvider.syncWals(); | ||
| }, |
There was a problem hiding this comment.
Missing
context.mounted guard after the await. While syncProvider.syncWals() itself does not consume context, adding the guard is consistent with the pattern in sync_page.dart and avoids a potential use_build_context_synchronously lint warning if the analysis options are tightened.
| onPressed: () async { | |
| if (await confirmSyncForCustomStt(context)) syncProvider.syncWals(); | |
| }, | |
| onPressed: () async { | |
| if (await confirmSyncForCustomStt(context) && context.mounted) syncProvider.syncWals(); | |
| }, |
|
@greptile-apps re-review |
Problem
Conversations recorded offline by users on a third‑party (custom) STT provider were getting auto‑synced to Omi and transcribed on Omi's servers, which counted against the free transcription limit and locked those conversations — even though the user uses their own STT and never touches Omi's STT live. This was the source of "my conversations are locked even though I use my own provider" complaints.
Root cause: the live path exempts custom‑STT users (
use_custom_sttskips Omi STT + credit checks), but the offline auto‑sync path has no such awareness — it uploads raw audio to/v2/sync-local-files, which transcribes on Omi and applies the free‑tier lock.What this PR does (app‑only, no backend changes)
For users with a custom STT provider enabled (
SharedPreferencesUtil().useCustomStt):capture_provider._autoSyncSessionWals(post‑session auto‑sync)capture_provider.recoverOrphanedWals(startup/reconnect recovery)sync_provider._autoUploadPendingPhoneFiles(in‑provider auto‑upload)home/pageoffline‑data‑detected auto‑sync triggersyncWals()triggers (sync page, Plan & Usage status card, Limitless card) via a shared helperconfirmSyncForCustomStt(). Non‑custom‑STT users are unaffected (helper is a no‑op that returnstrue).New strings localized across all 49 locales.
Scope / decisions
Related
Testing
flutter analyzeon all changed source files — no new issues (pre‑existing lints only, unrelated).flutter gen-l10n— clean, zero untranslated warnings; getters generated for all locales.Manual test plan