Your WhatsApp chats, analysed — without them ever leaving your phone.
Import an exported chat. Get statistics, a Wrapped-style story, a readable archive and a printable report — all computed locally, on your device.
| Dashboard | Wrapped | Import |
|---|---|---|
![]() |
![]() |
![]() |
Chat exports are one of the most personal files most people own — years of conversation in a single text file. Every tool that offers to analyse one asks you to upload it first.
Nuntius doesn't. Parsing, analysis, report generation and image export all run in a local isolate on the device. There is no server, no account, no analytics SDK and no crash reporter. The app makes exactly one network call in its default configuration — a font fetch that carries no chat data — and that can be removed too.
- Import anything WhatsApp gives you — the
.zipexport or a loose.txt, from local storage, Google Drive, or any document provider. No size limit. - Dashboard — overview and scores, activity over time, per-person breakdowns and awards, word and emoji analysis, milestones and badges, plus full-text search across every message.
- Wrapped — a swipeable story of the chat's highlights, with any card exportable as a 1080px image.
- Messages — the conversation read back, one month at a time, laid out like a chat.
- PDF report — cover, summary, participant table, vector charts, insights, timeline, and a method note explaining how every number was derived.
- Handles real exports — iOS and Android formats, day/month/year-first dates, 12- and 24-hour clocks, multi-line messages, localised media placeholders, system lines, and code-switched Roman-Urdu/Hindi text.
git clone https://github.com/yourhandle/nuntius.git
cd nuntius
flutter create --platforms=android,ios . # generates native runner dirs
flutter pub get
flutter runRequires Flutter 3.22+ (the code uses records and pattern matching).
The flutter create step only writes native boilerplate — it won't touch
anything in lib/.
- Open the chat
- Tap the chat name → Export chat
- Choose Without media
- Save it anywhere
Hand Nuntius the .zip or the .txt — the zip is unpacked for you.
lib/
├── core/
│ ├── constants/ analytics tuning, app metadata
│ ├── extensions/ date and duration helpers
│ ├── services/ import, archive, storage, image export
│ ├── theme/ colours, gradients, typography
│ ├── utils/ formatters, emoji, stopwords, text
│ └── widgets/ shared UI
├── features/
│ ├── analytics/ engine + 11 analyzers
│ ├── dashboard/ 6 tabs
│ ├── developer/ about screen
│ ├── home/ landing
│ ├── import_chat/ picker, zip, preview, progress
│ ├── messages/ month-by-month reader
│ ├── onboarding/ splash + intro
│ ├── parser/ WhatsApp format patterns
│ ├── reports/ saved chats, PDF generation
│ ├── settings/ theme, animation, storage
│ └── wrapped/ story cards, share sheet
├── models/ plain data classes
├── providers/ Riverpod
├── repositories/ Hive + SharedPreferences
└── routes/ go_router
~12,500 lines across 73 files. Clean architecture, Riverpod for state, go_router for navigation.
The import path
Parsing and analysis run in one isolate, and the result returns via
Isolate.exit — which hands over the memory rather than copying it. On a
250,000-message export that's the difference between a smooth progress bar and a
stalled one.
The file is streamed twice — once to detect date order, once to build messages — and never held in memory as raw text. Hence no file size limit: peak memory is set by the parsed message list, not by the file.
All analyzers are fed from a single loop through a shared MessageContext that
computes tokens, emoji and word counts once, lazily. Adding a statistic is one
class and one line, not another pass.
What gets stored
The full analytics are not serialised. Instead the export is copied into app-private storage, a small JSON index entry goes into Hive, and reopening re-parses and re-analyses in an isolate.
Tiny storage, no serialisation layer to drift from the models, and analyzer improvements apply retroactively to every saved chat.
Stored files are addressed by report id, resolved at read time — never by an absolute path saved earlier. On iOS the app container is addressed through a UUID that changes on reinstall, so files survive while stored paths pointing at them don't. Only the id is durable, so only the id is trusted.
Design decisions
Manrope, not Poppins — tall x-height and tabular figures keep big statistics legible and stop numbers jittering as they count up.
The double tick — WhatsApp's delivery tick reused as a structural device: the Wrapped progress indicator, the splash logo, the empty-state illustration. Borrowed from the subject's own vernacular rather than invented.
Scores are explainable — balance is normalised Shannon entropy (so it
generalises to group chats), consistency is sqrt(activeDays/totalDays),
responsiveness is 100/(1 + medianMinutes/20). The PDF states the formulas.
Awards go on rates, not totals, gated at ≥20 messages, so the person who talks most doesn't sweep every category.
Word clouds wrap, they don't spiral — spiral packing is unreadable on a phone. Fixed seed, so the same chat always yields the same layout.
AutoGrid, not GridView.count — a fixed childAspectRatio decides a
cell's height before its text is laid out, so any card whose copy wraps one line
further than expected overflows. AutoGrid measures rows intrinsically, making
overflow unrepresentable rather than merely unlikely.
One animation setting for everything — it drives timeDilation, the
framework-wide duration multiplier, so a single choice governs counters, charts,
the story and route transitions. "Off" sets MediaQuery.disableAnimations
instead, the same flag OS reduce-motion uses.
Supporting other platforms
The parser is the only WhatsApp-specific part. ParsedChat and ChatMessage
carry nothing platform-specific, and everything downstream reads those.
Telegram, Signal or Messenger support means writing one more parser that produces
a ParsedChat.
No chat content is transmitted, ever. There is no HTTP client anywhere in the parser, analytics, storage or reporting paths.
- imports parsed in a local isolate
- saved copies in app-private storage, deletable from Settings
- PDFs and images go through the OS share sheet, which you control
- the developer screen hands URLs to the OS browser; nothing is fetched in-app
The one exception is the google_fonts fetch above, which carries no chat data.
Xcode only runs on macOS, but you can rent one by the minute.
Build — GitHub Actions on a macos-14 runner: flutter build ios --release --no-codesign, then zip Runner.app into a Payload/ folder to make an IPA,
and download the artifact. macOS runner minutes bill at 10× on private repos.
Install — Sideloadly on Windows: plug the iPhone in over USB, drop the IPA in, sign in with a free Apple ID. Free signing allows 3 apps at a time, each expiring after 7 days.
You get a release build to tap through — no hot reload, no breakpoints, no device logs. For real debugging, rent a cloud Mac and use the Simulator, though it won't honestly reproduce file-picker or share-sheet behaviour.
- system text scale honoured, clamped 0.85×–1.6× so big-number layouts survive
- reduce-motion respected automatically, with a manual override in Settings
- colour is never the only signal — charts and colour-coded rows carry labels
- tooltips on every icon-only button
- Telegram and Signal parsers
- Group-chat-specific statistics
- Widget test coverage
- Bundled Manrope to remove the last network call
Issues and pull requests welcome. If you're reporting a parsing bug, the most useful thing you can include is a small anonymised excerpt showing the line format that failed — a few lines with names and content replaced is plenty. Please don't attach a real export.
flutter analyze
flutter testfl_chartandpdfboth move their APIs between minor versions; if the build fails,dashboard/charts.dartandreports/pdf_report_service.dartare the first places to look- the PDF renders emoji only when the optional fonts are bundled
- tests cover the parser and analytics engine; UI coverage is thin
MIT License — see LICENSE for details.
Grab the latest APK from the Releases page — Nuntius 1.0.0.
📱 iOS — coming soon.
Built with Flutter. Nothing leaves your device.


