Files
ledga/LEDGA_REDESIGN.md
T
ngenohkevin 51264301a8 v1.3.0: major redesign — bento UI, Insights, Goals, multi-SIM, in-app updates
UI overhaul
- Modern fintech bento design system (LedgaText, Space, Radius, Motion).
- Inter font via Google Fonts.
- New PillTabBar + LedgaTopBar; 4-tab IA: Home / Activity / Insights / You.
- Home rewritten as bento cards; new AccountChip leading slot.
- Activity wraps Transactions + Trends behind a segmented control.
- Trends gains a GitHub-style heatmap calendar.
- You consolidates Settings into grouped bento cards.

Insights (Phase C)
- Rule engine + 6 rules: anomaly (z-score), recurring detection, fee tips,
  Fuliza outstanding, Fuliza auto-pay pairing, positive nudges.
- Daily InsightWorker (3 AM); generates on screen entry for first-time users.
- Natural-key dedup so dismiss/snooze state survives re-runs.
- Home teaser surfaces the top active insight.

Goals (Phase D)
- New Goal entity + GoalContribution table.
- 3 contribution rules: AllSavingsDeposits, ToRecipient, Manual.
- Rule-based contributions compute live via SQL aggregation (no
  duplicate rows). Manual rule uses goal_contributions table.
- GoalsScreen with progress rings + status badges; AddGoalSheet;
  GoalDetailScreen with contributing-transactions list.
- "Add to goal" entry in TransactionDetailSheet for Manual goals.

Multi-SIM (Phase E)
- MpesaAccount entity captured per-subscription on SMS receive
  (SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX with legacy fallback).
- READ_PHONE_STATE permission (optional; falls back to "Line N" labels).
- Every screen-facing query is account-scoped via TransactionRepository
  composing with the selected-account flow.
- AccountSwitcherSheet on Home; AccountsScreen with rename / recolor /
  set-primary; BackfillScreen with 3-prong attribution (SMS-inbox auto,
  date range, per-transaction manual via TransactionDetailSheet).

In-app updates (Phase F + G)
- Chunked APK download with progress reporting.
- UpdateRepository state machine (Idle → Downloading → Ready → Failed).
- Full-screen UpdateScreen with parsed What's new / Fixes sections.
- "Remind me later" persists dismissed version.
- Background UpdateCheckWorker pre-fetches APK on unmetered network.
- Home banner: "available" vs "ready to install" copy variants.
- Proactive REQUEST_INSTALL_PACKAGES permission prompt with settings
  deep-link before firing the installer intent.

Parser quality
- Hustler Fund "sent KshX to NAME" amount-first phrasing now matches.
- "Give cash to AGENT" agent-deposit format recognized as DEPOSIT.
- Fuliza auto-pay recipient labels rewritten as "Fuliza overdraft
  (auto-cleared / auto-partial)" for clearer transaction lists.
- 8 new real-world regression fixtures from sanitized export.
- Audit: 0 UNKNOWN against 6,565-transaction real history (was 838).

Database
- v3 schema: mpesa_accounts, goals, goal_contributions, insights tables.
- transactions gains accountId (FK), note.
- Explicit MIGRATION_1_2 + MIGRATION_2_3.

Tests
- 65 unit tests across parser (33), rule engine (14), goal rules (8),
  update logic (8), parser audit (1), insights dry-run (1).
- Two test suites (audit, dry-run) read the local export when present,
  skip cleanly in CI.

Design spec
- LEDGA_REDESIGN.md captures the full direction + tokens + per-screen
  wireframes + phased PR plan.

versionCode 3, versionName 1.3.0.
2026-05-16 13:17:54 +03:00

944 lines
59 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ledga — Major Redesign Spec (v2)
> Target visual direction: **Modern Fintech Bento**. Soft neutral surfaces, M-Pesa green as a single accent, big confident numbers, generous whitespace, rounded everything. Feels at home next to Revolut / Cash App / Monzo while staying friendly for a Kenyan family audience.
>
> Scope: full UI overhaul + four new feature areas:
> 1. **Insights** — smart, derived suggestions (recurring detection, fee tips, anomaly callouts)
> 2. **Goals** — savings targets with progress + ETA
> 3. **Multi-SIM** — switch between two M-Pesa lines on a dual-SIM device
> 4. **In-app update flow** — surface release notes + one-tap install when a new APK is published
---
## 1. Design Tokens
These are the canonical names. The Compose `Color.kt` / `Theme.kt` files should mirror them 1:1. (They're also already saved into the pencil document via `set_variables`, so when Pencil starts cooperating we can wire them up as variables there too.)
### 1.1 Color — Light
| Token | Hex | Use |
|----------------------|-----------|--------------------------------------------------------|
| `--bg` | `#FAFAF7` | Page background (warm off-white, not stark) |
| `--surface` | `#FFFFFF` | Card / sheet background |
| `--surface-2` | `#F2F2EE` | Subtle elevation, chip backgrounds |
| `--ink` | `#0E1311` | Primary text, big numbers |
| `--ink-2` | `#3A4240` | Body text |
| `--muted` | `#6B7270` | Secondary text, captions |
| `--muted-2` | `#A4A8A5` | Disabled, hints |
| `--line` | `#E8E8E2` | Dividers, hairlines |
| `--accent` | `#10A37F` | M-Pesa-ish green, but de-saturated for premium feel |
| `--accent-soft` | `#E2F3EC` | Accent tints (chip backgrounds, progress tracks) |
| `--accent-deep` | `#0E7C5F` | Accent text on light, hover states |
| `--ink-on-accent` | `#FFFFFF` | Text on accent surfaces |
| `--danger` | `#D14B4B` | Destructive, over-budget, errors |
| `--danger-soft` | `#FBE8E8` | Danger backgrounds |
| `--warning` | `#E7A82C` | "Budget at 80%", fuliza outstanding |
| `--warning-soft` | `#FBEFD2` | Warning backgrounds |
| `--inflow` | `#1F8B5A` | Received / deposit / refund amounts |
| `--outflow` | `#0E1311` | Outflow uses `--ink` (not red) — red is for alerts only|
### 1.2 Color — Dark
Same token names, different values. Designed for OLED-friendly true-blacks while keeping the accent vivid.
| Token | Hex |
|----------------------|-----------|
| `--bg` | `#0A0C0B` |
| `--surface` | `#15191B` |
| `--surface-2` | `#1E2326` |
| `--ink` | `#F4F6F5` |
| `--ink-2` | `#C9CFCC` |
| `--muted` | `#8E948F` |
| `--muted-2` | `#5A615D` |
| `--line` | `#262B2D` |
| `--accent` | `#3CD9A8` | brighter on dark for pop |
| `--accent-soft` | `#0F2A23` |
| `--accent-deep` | `#85F2C8` |
| `--ink-on-accent` | `#06120E` |
| `--danger` | `#FF7A7A` |
| `--danger-soft` | `#2A1414` |
| `--warning` | `#FFC25E` |
| `--warning-soft` | `#2A2009` |
| `--inflow` | `#3CD9A8` |
| `--outflow` | `#F4F6F5` |
### 1.3 Category palette (same on both themes — saturated enough to stay distinct)
```
Groceries #4CAF50 Transport #3478F6 Bills #FF9F2D
Airtime/Data #A155F0 Food #EC4072 Send Money #6E7B8B
Received #06B6D4 Withdrawal #8B5A3C Deposit #7CC242
Shopping #FF6A3D International #4F58D9 Savings/Loans #089B91
Other #9E9E9E
```
Always paired with a 12% tint as the icon-tile background, full color for the icon glyph itself.
### 1.4 Typography
Font: **Inter** (variable) everywhere. Numbers use `font-feature-settings: "tnum"` (tabular figures) so balances align in columns.
| Style | Size | Weight | Line | Letter-spacing | Use |
|----------------|------|--------|------|-----------------|--------------------------------------------------|
| Display XL | 56 | 700 | 1.05 | -1.5 | Onboarding hero amounts (rare) |
| Display L | 44 | 700 | 1.05 | -1.0 | Home "Spent this month" |
| Display M | 32 | 700 | 1.1 | -0.5 | Goal current amount, trend hero |
| Title L | 24 | 700 | 1.2 | -0.2 | Screen titles (Home, Transactions, …) |
| Title M | 20 | 600 | 1.25 | -0.1 | Section headers, sheet titles |
| Title S | 17 | 600 | 1.3 | 0 | Card titles |
| Body L | 16 | 500 | 1.4 | 0 | Transaction recipient, primary body |
| Body M | 14 | 500 | 1.4 | 0 | Secondary body, descriptions |
| Caption | 12 | 500 | 1.3 | 0.1 | Day labels, meta info |
| Overline | 11 | 600 | 1.3 | 0.5 (uppercase) | Tab labels, section eyebrows |
| Mono S | 13 | 500 | 1.4 | 0 | Transaction codes (use system mono) |
### 1.5 Spacing scale
`4 · 8 · 12 · 16 · 20 · 24 · 32 · 48 · 64`
- Screen horizontal padding: **20**
- Card inner padding: **20** (compact cards 16)
- Section vertical gap: **24**
- Tight group vertical gap: **12**
### 1.6 Radius
`xs:8 · sm:12 · md:16 · lg:24 · xl:32 · pill:999`
- Cards: **24** (lg)
- Sheets: **32 top-only**
- Inputs / chips: **pill**
- Tab bar pill: **36**
- Icon tiles: **14**
### 1.7 Elevation
No drop shadows on light theme — depth comes from **1px `--line` borders + subtle surface-2 wells**. On dark theme, no shadows either — depth via surface stepping (`--surface` over `--bg`).
The only exception: the floating tab bar uses a soft drop shadow on light (y:8, blur:24, color: `#0E1311 @ 6%`).
### 1.8 Motion
| Token | Duration | Easing | Use |
|------------------|----------|-----------------------------------|------------------------------------|
| `motion-fast` | 120ms | `cubic-bezier(0.2, 0, 0, 1)` | Press states, ripples |
| `motion-base` | 240ms | `cubic-bezier(0.2, 0, 0, 1)` | Sheet open, chip select |
| `motion-emph` | 360ms | `cubic-bezier(0.2, 0, 0, 1)` | Screen enter, hero number flip |
| `motion-spring` | spring(stiffness=300, damping=30) | Tab switch, FAB scale |
Numbers (balances, totals) **animate from old → new value** when changed (counting up/down). Spec: 600ms `easeOutCubic`. Use Compose `animateFloatAsState` + `String.format`.
---
## 2. Component Inventory
### 2.1 Atoms
- **Avatar** 40 / 32 / 24 — circular, initial inside on `--surface-2`. Used in account switcher, transaction rows for person-to-person.
- **CategoryIcon tile** 40×40 — 14-radius rounded square, category color @ 12% bg, glyph in full color.
- **Chip / Filter** — pill, 32 height, 12px horizontal padding, `--surface-2` bg, `--ink` text. Selected: `--accent-soft` bg, `--accent-deep` text, 1px `--accent` border.
- **Switch** — Material 3 with `--accent` selected track.
- **TextField** — 56 height, pill radius, `--surface-2` bg, no outline. Floating label collapses on focus.
- **Button** — Primary (filled accent, pill, 52h), Secondary (1px `--line`, `--surface`), Ghost (text only), Danger (filled `--danger`).
- **Badge** — 20h pill, 8px horizontal, caption text. Variants: neutral, success, warning, danger.
- **ProgressBar** — 8h pill, `--surface-2` track, `--accent` fill. Over-budget: `--danger` fill.
- **ProgressRing** — 56 / 80 / 120 sizes, 6 stroke, same color rules as ProgressBar.
### 2.2 Molecules
- **TransactionRow**
```
┌────────────────────────────────────────────────┐
│ [🛒] Naivas Supermarket -Ksh 1,200 │
│ Groceries · 2:15 PM Bal 3,500 │
└────────────────────────────────────────────────┘
```
Icon (40), middle stack (recipient L, meta M muted), right stack (amount L, balance Caption muted). 16px vertical padding, no borders between rows — separated only by 12px gap inside a card.
- **StatCard** — title (Overline muted), value (Display M ink), delta chip (Caption with ▲/▼).
- **BentoTile** — generic 24-radius card with optional header row (icon + title + trailing chevron) and content slot.
- **DayHeader** — sticky inside the transactions list. "Today · Ksh 3,420" left, weekday caption right.
- **AccountChip** — used in Home header for multi-SIM. Avatar + carrier + chevron, opens AccountSwitcherSheet.
- **InsightCard** — illustrated, 24-radius, accent-soft bg, icon top-left, title + body, optional CTA.
- **GoalCard** — ProgressRing left, name + amount stack right, ETA badge bottom.
### 2.3 Organisms
- **TopBar** — sticky on every screen. Three slots: leading (Avatar + greeting OR back button), title (only on detail screens), trailing (icons: notifications, settings).
- **PillTabBar** — floating, bottom-anchored, 4 tabs: Home / Activity / Insights / You.
- Spec from mobile-app guideline: container padding 12 top / 21 sides+bottom, pill 62h with 36 radius, items vertical (18 icon + 10 uppercase label), active = `--accent` fill + `--ink-on-accent`.
- Slots: Home, Activity (combines Transactions + Trends in one screen with segmented top), Insights, You (profile/settings/goals).
- FAB-like + center action removed to keep the bar honest — actions belong inside screens.
- **BottomSheet** — radius 32 top-only, 4-wide 40-long grab handle, 24px padding, max 90% screen height.
- **EmptyState** — 240 illustrated SVG centered, Title M, Body M muted, single primary CTA.
---
## 3. Information Architecture
Old IA: Home / Transactions / Trends / Settings (4 tabs).
New IA: **Home / Activity / Insights / You** (4 tabs) — but with more inside.
```
┌─ Home — overview, hero spend, quick stats, recent
│
├─ Activity — segmented [ Transactions · Trends ]
│ ├─ Transactions — day-grouped list, search, filter chips
│ └─ Trends — charts, top merchants, fees, heatmap
│
├─ Insights — smart cards (auto-generated)
│ • recurring detection
│ • fee tips
│ • spending anomalies
│ • category trends
│
└─ You — profile + grouped settings + goals
├─ Accounts — multi-SIM management
├─ Goals — list + add
├─ Budgets
├─ Appearance
├─ Notifications
├─ Data
├─ Backup
└─ About / Updates
```
**Why this IA**:
- "Activity" merges Transactions + Trends because users always toggle between "what happened" and "what's the pattern" — they shouldn't be two tabs apart.
- "You" pulls in Goals because goals are personal, not screen-scoped. It also hosts Accounts (multi-SIM).
- "Insights" earns top-level because the feature only works if users see it without going looking.
---
## 4. Screen Specs
For each screen below: **status bar 62**, then **wrapper** with horizontal padding `--pad-screen` (20) and gap `--gap-7` (24) between sections, then **pill tab bar** floating at the bottom (where shown).
Bottom padding on the scroll content: **120** (tab bar height 62 + 21 padding + 32 breathing room) so the last item is never hidden behind the bar.
### 4.1 Onboarding (4 steps, swipeable + dotted progress)
**Step 1 — Welcome**
```
┌──────────────────────────────────────────────┐
│ │
│ │
│ ● ○ ○ ○ │
│ │
│ ┌──────┐ │
│ │ L │ (logo, 88px) │
│ └──────┘ │
│ │
│ Track every M-Pesa shilling. │
│ Automatically. │
│ │
│ Ledga reads your M-Pesa SMS and turns │
│ it into a calm, private spending │
│ picture. Nothing leaves your phone. │
│ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Get started → │ │
│ └────────────────────────────────────┘ │
│ │
│ I'll set this up later │
└──────────────────────────────────────────────┘
```
- Hero text: Title L
- Body: Body L muted, max-width ~320
- Primary CTA: 52h pill filled accent
- "Set up later" = Ghost button, only proceeds with read-only mode
**Step 2 — SMS permission (the trust moment)**
```
┌──────────────────────────────────────────────┐
│ ● ● ○ ○ │
│ │
│ ┌──────┐ │
│ │ 📩 │ (icon tile, 56) │
│ └──────┘ │
│ │
│ Let Ledga read M-Pesa SMS │
│ │
│ • Only messages from MPESA are read │
│ • Nothing is sent to a server │
│ • You can revoke this in system settings │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ 🔒 Your data is on this device only │ │
│ │ and is backed up to your Google │ │
│ │ Drive if you turn it on. │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Allow SMS access → │ │
│ └────────────────────────────────────┘ │
│ │
│ Not now │
└──────────────────────────────────────────────┘
```
- The accent-soft callout card under the bullets is the "trust signal" — keep it.
- "Not now" routes to a minimal manual-entry mode (out of scope this release, just a stub screen).
**Step 3 — Import history**
```
┌──────────────────────────────────────────────┐
│ ● ● ● ○ │
│ │
│ Bring in your history? │
│ │
│ We can scan the M-Pesa messages already │
│ on your phone to build a full picture. │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ 📊 342 M-Pesa messages found │ │
│ │ Going back to Aug 2024 │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Import all 342 messages → │ │
│ └────────────────────────────────────┘ │
│ │
│ Start fresh │
│ │
│ ───── while importing ───── │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Importing… │ │
│ │ ▓▓▓▓▓▓▓▓▓░░░░░░ 68% │ │
│ │ 231 of 342 messages │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
```
- Show found-count BEFORE asking, so the user can decide informedly.
- Progress card replaces the CTA when import is running, then auto-advances.
**Step 4 — Multi-SIM detection (NEW, optional)**
Only shown if two active M-Pesa lines are detected via subscription IDs in the SMS data.
```
┌──────────────────────────────────────────────┐
│ ● ● ● ● │
│ │
│ We spotted two M-Pesa lines │
│ │
│ You can name them so it's clear which is │
│ which. Both are tracked separately and you │
│ can switch from the home screen anytime. │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ ⓅⓛⓁ Line 1 Safaricom · 07XX…23 │ │
│ │ Name ┌──────────────────────────┐ │ │
│ │ │ Personal × │ │ │
│ │ └──────────────────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ ⓆⓀ Line 2 Safaricom · 07XX…87 │ │
│ │ Name ┌──────────────────────────┐ │ │
│ │ │ Business × │ │ │
│ │ └──────────────────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ You're set → │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
```
### 4.2 Home
```
┌──────────────────────────────────────────────┐
│ Status bar │
├──────────────────────────────────────────────┤
│ │
│ ⒶPersonal ▾ 🔔 ⚙ │ ← AccountChip + 2 icon buttons
│ │
│ Hi, Kevin │ Title L
│ Here's how March is going. │ Body M muted
│ │
│ ┌──────────────────────────────────────────┐ │ ← HERO bento card
│ │ SPENT THIS MONTH › │ │ overline muted
│ │ │ │
│ │ Ksh 45,200 │ │ Display L
│ │ ▼ 12% vs February · Ksh 320 in fees │ │ Body M w/ inline chip
│ │ │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓░░░ 78% of 60,000 budget │ │ progress
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │ ← 2-up small bentos
│ │ BALANCE │ │ TOP CATEGORY │ │
│ │ Ksh 12,500 │ │ 🛒 Groceries │ │
│ │ from last tx │ │ Ksh 18,200 │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Donut bento
│ │ Where it went ›│ │
│ │ │ │
│ │ ○─◐─● Groceries 40%│ │
│ │ ╱ donut ╲ Transport 18│ │
│ │ │ 45,200 │ Bills 14│ │
│ │ ╲ ╱ Food 11│ │
│ │ ───── Other 17│ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Insights teaser
│ │ ✨ INSIGHTS ›│ │
│ │ │ │
│ │ You're spending 40% more on transport │ │
│ │ this week. 3 Uber trips, all weekends. │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Recent activity See all│ ← section header
│ ┌──────────────────────────────────────────┐ │
│ │ [🛒] Naivas −Ksh 1,200 │ │
│ │ 2:15 PM Bal 3,500 │ │
│ │ ───────────────────────────────────── │ │ hairline
│ │ [📱] Airtime −Ksh 100 │ │
│ │ 12:00 PM │ │
│ │ ───────────────────────────────────── │ │
│ │ [💰] Jane Doe +Ksh 5,000 │ │ inflow color
│ │ 11:00 AM │ │
│ └──────────────────────────────────────────┘ │
│ │
│ (UPDATE BANNER, conditional, see §4.10) │
│ │
│ ⌒ ⌒ ⌒ ⌒ pill tab bar floating │
└──────────────────────────────────────────────┘
```
Behaviors:
- The hero `Spent` number animates from last value to new on receipt of a new SMS.
- Tapping the hero card → Trends (Activity tab → Trends segment).
- Tapping "Balance" small card → reveals "balance history" sparkline in-place (300ms expand, doesn't navigate).
- Tapping "Top category" → filter Transactions list by that category.
- Donut card uses Vico segments; legend is right-justified.
- Insights teaser shows the highest-priority unresolved insight; rotates daily.
### 4.3 Activity — Transactions segment
```
┌──────────────────────────────────────────────┐
│ ‹ │
│ Activity │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ [ Transactions ] Trends │ │ ← segmented (pill)
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 🔍 Search merchants, codes… │ │ ← search input
│ └──────────────────────────────────────────┘ │
│ │
│ All · Sent · Received · Bills · Goods … ‹ ›│ ← horizontal chip row
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ TODAY · Out 3,420 · In 5,000 │ │ ← sticky day header
│ │ │ │
│ │ [🛒] Naivas −Ksh 1,200 │ │
│ │ [📱] Airtime −Ksh 100 │ │
│ │ [💰] Jane Doe +Ksh 5,000 │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ YESTERDAY · Out 2,150 │ │
│ │ [🍔] KFC Westgate −Ksh 850 │ │
│ │ [🚗] Uber −Ksh 1,300 │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ WED 18 MAR │ │
│ │ [⚡] KPLC −Ksh 2,500 │ │
│ │ Account 12345678 │ │
│ └──────────────────────────────────────────┘ │
│ │
│ pill tab bar │
└──────────────────────────────────────────────┘
```
Interactions:
- Tap row → TransactionDetailSheet (§4.9).
- Long-press row → quick category picker (mini sheet).
- Filter chips multi-select; the row scrolls horizontally; "Customize…" at end opens the full filter sheet.
- Search ranks by transaction code prefix > merchant name fuzzy > recipient phone.
### 4.4 Activity — Trends segment
```
┌──────────────────────────────────────────────┐
│ Activity │
│ │
│ [ Transactions ] [ Trends ] │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 7D [ 30D ] 90D 1Y Custom │ │ ← period selector pill
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Hero compare bento
│ │ NET CASHFLOW │ │
│ │ │ │
│ │ −Ksh 39,880 ▼ 8% vs prev 30D │ │
│ │ Out 45,200 In 5,320 │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Daily bars
│ │ Daily spending │ │
│ │ │ │
│ │ ▁▂█▃▅▂▁▄▆▂▁▃▅▇▂▁▃▆▄▂▃▅▇▃▁▂▄▆▂▁ │ │
│ │ 1 5 10 15 20 25 30 │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Heatmap calendar (NEW)
│ │ Spending heatmap › │ │
│ │ │ │
│ │ M T W T F S S │ │
│ │ □ □ ▤ ▤ ▥ ▦ ■ │ │
│ │ ▤ ■ ▦ ▤ ■ ▦ ▥ │ │ 4 intensity steps
│ │ ▦ ▤ ▥ ■ ▤ □ ▦ │ │
│ │ ▤ ▥ ▦ ▤ ▤ ■ ▥ │ │
│ │ ▦ ▤ □ ▥ □ ▤ ▦ │ │
│ │ low ▢▢▢▢ high │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Top merchants
│ │ Top merchants › │ │
│ │ 🛒 Naivas 5 visits Ksh 8,400 │ │
│ │ 🚗 Uber 4 trips Ksh 5,200 │ │
│ │ 🍔 KFC 3 visits Ksh 2,550 │ │
│ │ ⚡ KPLC 1 bill Ksh 2,500 │ │
│ │ 📱 Airtime 9 buys Ksh 900 │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Fees summary (NEW)
│ │ 💸 FEES PAID │ │
│ │ Ksh 320 over 30 days │ │
│ │ ▲ 12% vs previous 30D │ │
│ │ Most fees came from Withdraw (Ksh 215) │ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
```
The heatmap is the high-value new visual — turns "you spent more last Friday" into something instantly readable. 4 intensity steps using `--accent` opacity 8/24/56/88%.
### 4.5 Insights (NEW)
Algorithmically generated. Each card has a "Got it" button (dismiss for 30 days) and optional secondary action (e.g., "See transactions").
```
┌──────────────────────────────────────────────┐
│ Insights │
│ 4 new this week │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Anomaly insight
│ │ 🚨 WATCH OUT │ │
│ │ You spent 40% more on transport this │ │
│ │ week than your typical week. │ │
│ │ 3 Uber trips on weekends · Ksh 4,800 │ │
│ │ [ See transactions ] [ Got it ] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Recurring detection
│ │ 🔁 RECURRING DETECTED │ │
│ │ KPLC seems to be a monthly bill. │ │
│ │ Avg Ksh 2,420 · last 3 months │ │
│ │ [ Add to budget ] [ Got it ] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Fee tip
│ │ 💡 FEE TIP │ │
│ │ You paid Ksh 215 in agent withdrawal │ │
│ │ fees this month. Withdrawing fewer, │ │
│ │ larger amounts could save ~Ksh 90. │ │
│ │ [ Show me how ] [ Got it ] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Positive nudge
│ │ ✨ NICE │ │
│ │ Groceries spending is down 14% this │ │
│ │ month. Keep it up! │ │
│ │ │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Fuliza warning
│ │ ⚠ FULIZA │ │
│ │ Outstanding Fuliza: Ksh 500 │ │
│ │ Last borrowed 3 days ago. │ │
│ │ [ How Fuliza fees work ] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Want fewer of these? Settings → Insights │
│ │
└──────────────────────────────────────────────┘
```
Card variants (background colors):
- **Watch out / anomaly**: warning-soft bg, warning icon
- **Recurring**: surface bg, accent icon
- **Fee tip**: accent-soft bg
- **Positive nudge**: accent-soft bg, sparkles icon
- **Fuliza / danger**: danger-soft bg, danger icon
Generation cadence: re-evaluate on every new transaction (cheap rules) + weekly batch (heavier comparisons). All logic local, deterministic — no ML.
### 4.6 Goals (NEW, lives under "You")
```
┌──────────────────────────────────────────────┐
│ ‹ You │
│ Goals + │
│ │
│ ┌──────────────────────────────────────────┐ │ ← Active goal card
│ │ ◖◗ School fees │ │
│ │ 67% Term 2 │ │
│ │ Ksh 40,000 / 60,000 │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░ │ │
│ │ ETA 14 Apr · on track ✓ │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ ◖◗ Pi server budget │ │
│ │ 32% Hardware fund │ │
│ │ Ksh 8,000 / 25,000 │ │
│ │ ▓▓▓▓▓▓░░░░░░░░░░░░░░ │ │
│ │ ETA 12 Jul · slightly behind ⚠ │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ ◖◗ Eldoret trip │ │
│ │ 100% Completed Mar 4 🎉 │ │
│ │ Ksh 15,000 / 15,000 │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Suggested by your activity: │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 💡 You've moved Ksh 5,000 to M-Shwari │
│ │ each month for 3 months. Turn that │ │
│ │ into a goal? │ │
│ │ [ Create goal ] │ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
```
**Goal model**:
```kotlin
data class Goal(
val id: Long,
val name: String,
val targetAmount: Double,
val targetDate: LocalDate?, // nullable → no ETA, only progress
val contributionRule: ContributionRule,
val createdAt: Long,
val completedAt: Long?,
)
sealed class ContributionRule {
// count every M-Shwari/KCB deposit as contribution
object AllSavingsDeposits : ContributionRule()
// count outflows to a specific paybill (e.g. school fees paybill)
data class ToPaybill(val paybill: String) : ContributionRule()
// user manually marks a transaction as contributing
object Manual : ContributionRule()
}
```
Progress is derived. ETA = `(targetAmount - currentAmount) / avgMonthlyContribution * 30 days from today`.
**Add Goal sheet** (full screen sheet):
- Goal name
- Target amount
- Target date (optional)
- Contribution rule (radio: All savings · Specific paybill · Manual mark)
- Color picker (one of the category colors)
### 4.7 You
```
┌──────────────────────────────────────────────┐
│ You │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ ⒶⒶ Kevin │ │ ← profile card
│ │ 2 M-Pesa lines · 1,432 tx tracked │ │
│ │ [ Manage accounts ] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 🎯 Goals 3 ›│ │
│ │ 💰 Budgets 2 ›│ │
│ │ 🏷 Categories ›│ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 🌓 Appearance │ │
│ │ Theme System › │ │
│ │ Font size Medium › │ │
│ │ Accent ●●●●● › │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 🔔 Notifications │ │
│ │ Daily summary 8:00 PM │ │
│ │ Weekly summary Sun, 6 PM │ │
│ │ Budget alerts ● │ │
│ │ Large transaction alert OFF › │ │
│ │ Insights ● │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 🗄 Data │ │
│ │ Import SMS history › │ │
│ │ Re-parse 14 unknown messages › │ │
│ │ Export (CSV / JSON) › │ │
│ │ Clear all data › │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ ☁ Backup │ │
│ │ Google Drive ngenoh@… │ │
│ │ Last backup Today 3 AM │ │
│ │ Back up now › │ │
│ │ Restore › │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ ℹ About │ │
│ │ Version 1.0.0 │ │
│ │ [ NEW ] v1.1.0 available Update › │ │
│ │ What's new › │ │
│ │ Privacy › │ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
```
Settings rows are **grouped into bento cards** instead of being a flat list — same `--surface` cards, hairlines between rows inside a card, no hairline at edges. Each "group" = one bento.
### 4.8 Account Switcher Sheet (Multi-SIM)
Triggered from the AccountChip on Home.
```
┌──────────────────────────────────────────────┐
│ │
│ ━━━━ │ ← grab handle
│ │
│ Accounts │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ ⓅⓛⓁ Personal ● selected │ │ ← accent border + check
│ │ Safaricom · 07XX…23 │ │
│ │ Bal 12,500 · 1,200 tx │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ ⓆⓀ Business │ │
│ │ Safaricom · 07XX…87 │ │
│ │ Bal 4,300 · 232 tx │ │
│ └──────────────────────────────────────┘ │
│ │
│ View │
│ ( ) Selected account only │
│ (●) Combined view │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ + Add another line │ │
│ └──────────────────────────────────────┘ │
│ │
│ Rename · Recolor accounts in You → Accounts│
│ │
└──────────────────────────────────────────────┘
```
**Data model addition**:
```kotlin
@Entity(tableName = "mpesa_accounts")
data class MpesaAccount(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val subscriptionId: Int, // Android SubscriptionInfo id
val phoneNumber: String?, // E.164 if available
val displayName: String, // "Personal" / "Business"
val colorHex: String, // accent for tiles
val isPrimary: Boolean,
)
```
`Transaction.accountId: Long?` is added. Migration:
1. ALTER TABLE add column nullable.
2. Existing rows stay null = "default" account.
3. On next SMS receive, populate the `accounts` table from SubscriptionManager and stamp incoming transactions.
### 4.9 Transaction Detail Sheet
```
┌──────────────────────────────────────────────┐
│ ━━━━ │
│ │
│ [🛒] Naivas Supermarket │
│ BUY GOODS │
│ │
│ −Ksh 1,200.00 │ Display L outflow ink
│ cost Ksh 0.00 │ Caption muted
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Code RK31B7X4ZQ │ │
│ │ Date 21 Mar 2026, 2:15PM │ │
│ │ Balance after Ksh 3,500.00 │ │
│ │ Account Personal ⓅⓛⓁ │ │ ← only if multi-SIM
│ └──────────────────────────────────────┘ │
│ │
│ Category │
│ ┌──────────────────────────────────────┐ │
│ │ 🛒 Groceries › │ │ ← tap to change
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Mark as contribution to goal › │ │
│ └──────────────────────────────────────┘ │
│ │
│ Notes │
│ ┌──────────────────────────────────────┐ │
│ │ Tap to add note │ │
│ └──────────────────────────────────────┘ │
│ │
│ ⌄ Original SMS │ ← collapsible
│ ┌──────────────────────────────────────┐ │
│ │ RK31B7X4ZQ Confirmed. Ksh1,200.00 │ │ mono
│ │ paid to NAIVAS SUPERMARKET. on … │ │
│ └──────────────────────────────────────┘ │
│ │
│ [ Share ] [ Delete ] │
│ │
└──────────────────────────────────────────────┘
```
Notes:
- The "Mark as contribution" row appears only if user has at least one Goal with `Manual` rule.
- Delete only removes from app (the SMS remains in Android's SMS DB), and creates an UNDO snackbar.
### 4.10 In-app Update Flow
**Discovered (Home banner)** — slot just above Recent Activity:
```
┌──────────────────────────────────────────────┐
│ ✨ Ledga v1.1.0 is ready │
│ • Insights · Goals · Multi-SIM │
│ [ See what's new ] [ Update ›] │
└──────────────────────────────────────────────┘
```
- Background: `--accent-soft`, border: `--accent`.
- Dismissable. If dismissed it reappears in You → About.
**Full screen** — tap "See what's new":
```
┌──────────────────────────────────────────────┐
│ ‹ │
│ │
│ Ledga v1.1.0 │ Title L
│ March 2026 · 4.8 MB │ Body M muted
│ │
│ ┌──────────────────────────────────────────┐ │ hero illustration
│ │ [ release artwork ] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ What's new │
│ │
│ ✨ Insights tab — smart suggestions │
│ based on your spending patterns. │
│ │
│ 🎯 Goals — track savings towards rent, │
│ school fees, trips. │
│ │
│ Ⓢ Dual-SIM support — switch between │
│ two M-Pesa lines from the home screen. │
│ │
│ 🎨 Brand new design — bento layout, big │
│ numbers, dark theme polish. │
│ │
│ Fixes │
│ • Faster startup on Android 8 devices │
│ • Better Fuliza parsing │
│ • 14 small fixes from your feedback │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Download and install → │ │
│ └────────────────────────────────────┘ │
│ │
│ Remind me later │
└──────────────────────────────────────────────┘
```
**Installing** — replaces the CTA:
```
┌────────────────────────────────────┐
│ Downloading ▓▓▓▓▓▓▓▓░░░ 72% │
│ 3.4 MB of 4.8 MB │
└────────────────────────────────────┘
```
Then Android's standard installer intent fires.
---
## 5. Implementation Order
The redesign maps cleanly to phased PRs. Each phase is shippable.
### Phase A — Foundation (PR 1)
- Replace `Color.kt` with new token names + dark theme.
- Add `MpesaAccount` entity + DAO + migration.
- Add `Goal` entity + DAO + migration.
- Extend `Transaction` with `accountId: Long?`, `note: String?`.
- New `BentoCard`, `StatCard`, `Chip`, `ProgressRing` composables.
- New `TopBar` and `PillTabBar` composables.
### Phase B — Visual Overhaul (PR 2)
- Rewrite Home, Transactions, Trends with the new components, tokens, IA.
- New segmented "Activity" parent screen.
- New You screen consolidating Settings + new entries.
### Phase C — Insights (PR 3)
- Rule engine: anomaly (z-score against same-category rolling 4-week mean), recurring detection (3+ same merchant ≥21-day apart), fee tips, Fuliza warnings.
- `InsightsRepository` + `InsightWorker` (daily WorkManager).
- Insights screen + dismiss/snooze table.
### Phase D — Goals (PR 4)
- Goals screen + Add sheet + detail flow.
- Contribution attribution on transaction insert.
- "Mark as contribution" entry point in TransactionDetailSheet.
### Phase E — Multi-SIM (PR 5)
- Subscription detection on SMS receive (`SmsMessage.getSubscriptionId` / Android `SubscriptionManager`).
- Account switcher sheet.
- Filter on all queries via selected account or "combined".
- Onboarding step 4.
### Phase F — Update flow (PR 6)
- GitHub Releases polling.
- Banner + full-screen changelog.
- DownloadManager + install intent.
---
## 6. Open questions (decide before Phase A)
1. **Tab bar count**: keep 4 (Home / Activity / Insights / You) or 5 (split Trends out from Activity)? Recommendation: **4** — keeps thumb reach generous, Insights doesn't fight Trends.
2. **Multi-SIM detection**: Android only exposes `subscriptionId` on incoming SMS — not on already-imported ones. For historical imports we'll have to leave `accountId = null` and let the user assign in bulk. OK?
3. **Insight cadence**: notify on new insight (push) or only show in-app? Recommendation: **in-app only**, with a daily-summary mention if any new insights exist.
---
## 7. What's intentionally NOT in this redesign
- Web / desktop companion — out of scope, app stays mobile-only.
- Bill-pay shortcuts — Ledga is read-only; it tracks, not transacts.
- Bank-account integration — M-Pesa SMS only.
- Cloud sync between devices — backup-only via Google Drive; no per-device sync.
- AI summaries — Insights are deterministic rule output, not LLM output. Keeps the app offline and predictable.