diff --git a/.gitignore b/.gitignore index f0f2dd9..38bf9e4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ build/ *.class .kotlin/ .claude/ + +# Internal planning docs — kept locally, not pushed. +LEDGA_REDESIGN.md +PROJECT_PLAN.md diff --git a/LEDGA_REDESIGN.md b/LEDGA_REDESIGN.md deleted file mode 100644 index 5ce0ccf..0000000 --- a/LEDGA_REDESIGN.md +++ /dev/null @@ -1,943 +0,0 @@ -# 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. diff --git a/PROJECT_PLAN.md b/PROJECT_PLAN.md deleted file mode 100644 index b17ce6c..0000000 --- a/PROJECT_PLAN.md +++ /dev/null @@ -1,770 +0,0 @@ -# Ledga — M-Pesa Spending Tracker - -## Overview - -A native Android app that automatically tracks M-Pesa spending by reading SMS confirmations. Zero manual entry — install, grant SMS permission, done. Built for non-technical users with a clean, Canva-like UI. - -**Target users:** Family members (all Android), non-technical -**Distribution:** Direct APK install + in-app self-updates via GitHub Releases (no Play Store) -**Data model:** Fully offline, local-only per device. No accounts, no server. - ---- - -## Tech Stack - -| Component | Technology | -|-----------|------------| -| Language | Kotlin | -| UI Framework | Jetpack Compose + Material 3 | -| Local Database | Room (SQLite) | -| Background SMS | BroadcastReceiver + WorkManager | -| DI | Hilt (dependency injection) | -| Settings Storage | Jetpack DataStore (Preferences) | -| Charts | Vico (Compose-native charting library) | -| Notifications | Android NotificationManager + AlarmManager | -| Backup | Google Drive API (auto), manual file export | -| Self-Update | GitHub Releases API | -| Min SDK | API 26 (Android 8.0) | -| Build | Gradle (Kotlin DSL) | - ---- - -## Architecture - -``` -┌─────────────────────────────────────────────┐ -│ UI Layer │ -│ Jetpack Compose + Material 3 │ -│ ┌──────┐ ┌──────────┐ ┌──────┐ ┌────────┐ │ -│ │ Home │ │Transactions│ │Trends│ │Settings│ │ -│ └──────┘ └──────────┘ └──────┘ └────────┘ │ -├─────────────────────────────────────────────┤ -│ ViewModel Layer │ -│ StateFlow + Coroutines │ -├─────────────────────────────────────────────┤ -│ Repository Layer │ -│ TransactionRepo │ CategoryRepo │ │ -│ SettingsRepo │ BackupRepo │ │ -├─────────────────────────────────────────────┤ -│ Data Layer │ -│ ┌──────┐ ┌───────────┐ ┌─────────────┐ │ -│ │ Room │ │SMS Parser │ │Google Drive │ │ -│ │ DB │ │ │ │ Backup │ │ -│ └──────┘ └───────────┘ └─────────────┘ │ -├─────────────────────────────────────────────┤ -│ System Services │ -│ SMS BroadcastReceiver │ WorkManager │ -│ NotificationManager │ AlarmManager │ -└─────────────────────────────────────────────┘ -``` - ---- - -## SMS Parsing - -### Sender Filter -Only process SMS from sender: `MPESA` - -### Transaction Types & Patterns - -**1. Send Money** -``` -RK31B7X4ZQ Confirmed. Ksh500.00 sent to JOHN DOE 0712345678 on 21/3/26 at 1:30 PM. New M-PESA balance is Ksh1,200.00. Transaction cost, Ksh0.00. -``` -→ Extract: code, amount, recipient name, recipient phone, date/time, balance, cost - -**2. Buy Goods (Till)** -``` -RK31B7X4ZQ Confirmed. Ksh1,200.00 paid to NAIVAS SUPERMARKET. on 21/3/26 at 2:15 PM.New M-PESA balance is Ksh3,500.00. Transaction cost, Ksh0.00. -``` -→ Extract: code, amount, merchant name, date/time, balance, cost - -**3. Pay Bill** -``` -RK31B7X4ZQ Confirmed. Ksh2,500.00 paid to KPLC PREPAID. Account Number 12345678. on 21/3/26 at 3:00 PM. New M-PESA balance is Ksh1,000.00. Transaction cost, Ksh0.00. -``` -→ Extract: code, amount, business name, account number, date/time, balance, cost - -**4. Withdraw (Agent)** -``` -RK31B7X4ZQ Confirmed.You have withdrawn Ksh1,000.00 from JAMES AGENT 543210 on 21/3/26 at 4:00 PM.New M-PESA balance is Ksh500.00. Transaction cost, Ksh28.00. -``` -→ Extract: code, amount, agent name, agent number, date/time, balance, cost - -**5. Withdraw (ATM)** -``` -RK31B7X4ZQ Confirmed. You have withdrawn Ksh5,000.00 from an ATM on 21/3/26 at 4:30 PM. New M-PESA balance is Ksh2,000.00. Transaction cost, Ksh34.00. -``` -→ Extract: code, amount, date/time, balance, cost -→ Note: No agent name/number — just "from an ATM" - -**6. Deposit** -``` -RK31B7X4ZQ Confirmed.You have deposited Ksh5,000.00 to your M-PESA account on 21/3/26 at 10:00 AM.New M-PESA balance is Ksh5,500.00. -``` -→ Extract: code, amount, date/time, balance - -**7. Received Money** -``` -RK31B7X4ZQ Confirmed.You have received Ksh2,000.00 from JANE DOE 0798765432 on 21/3/26 at 11:00 AM.New M-PESA balance is Ksh7,500.00. -``` -→ Extract: code, amount, sender name, sender phone, date/time, balance - -**8. Airtime Purchase (Self)** -``` -RK31B7X4ZQ Confirmed. Ksh100.00 of airtime purchased on 21/3/26 at 12:00 PM.New M-PESA balance is Ksh7,400.00. Transaction cost, Ksh0.00. -``` -→ Extract: code, amount, date/time, balance, cost - -**9. Airtime Purchase (For Others)** -``` -RK31B7X4ZQ Confirmed. You bought Ksh100.00 of airtime for 0712345678 on 21/3/26 at 12:30 PM.New M-PESA balance is Ksh7,300.00. Transaction cost, Ksh0.00. -``` -→ Extract: code, amount, recipient phone, date/time, balance, cost - -**10. M-Pesa Global (International Transfer)** -``` -RK31B7X4ZQ Confirmed. Ksh5,000.00 sent to JOHN DOE +44712345678 (United Kingdom) via M-PESA Global on 21/3/26 at 1:30 PM. New M-PESA balance is Ksh10,000.00. Transaction cost, Ksh150.00. -``` -→ Extract: code, amount, recipient name, recipient phone (international), destination country, date/time, balance, cost - -**11. Fuliza (Borrow)** -``` -RK31B7X4ZQ Confirmed. Ksh500.00 sent to JOHN DOE 0712345678 on 21/3/26 at 1:30 PM. New M-PESA balance is Ksh0.00. Fuliza M-PESA amount is Ksh500.00. Fuliza M-PESA outstanding amount is Ksh500.00. -``` -→ Extract: all standard fields + fuliza amount, fuliza outstanding - -**12. Fuliza Repayment** -``` -RK31B7X4ZQ Confirmed. You have paid Ksh200.00 to Fuliza M-PESA on 21/3/26 at 2:00 PM. Fuliza M-PESA outstanding amount is Ksh300.00. -``` -→ Extract: code, amount, date/time, fuliza outstanding - -**13. Fuliza Reversal** -``` -RK31B7X4ZQ Confirmed. Fuliza M-PESA of Ksh500.00 has been reversed on 21/3/26 at 3:00 PM. Fuliza M-PESA outstanding amount is Ksh0.00. -``` -→ Extract: code, amount, date/time, fuliza outstanding - -**14. Reversal** -``` -RK31B7X4ZQ Confirmed. Transaction RJ12345678 has been reversed. Your account balance is Ksh2,000.00. -``` -→ Extract: code, reversed transaction code, balance - -### M-Shwari & KCB M-Pesa (Acknowledged, Not Parsed) - -These services send SMS from the `MPESA` sender but are savings/loan products, not spending transactions: -- **M-Shwari:** `Confirmed. Ksh1,000.00 transferred to M-Shwari account...` / `Ksh1,000.00 transferred from M-Shwari...` -- **KCB M-Pesa:** `Confirmed. Ksh2,000.00 transferred to KCB M-Pesa account...` / loan disbursements - -**Strategy:** Detect these by keywords ("M-Shwari", "KCB M-Pesa") and store with type `MSHWARI` or `KCB_MPESA`. Show them in the transaction list but don't count them as spending. They're internal transfers between M-Pesa wallet and savings/loan products. - -### Parser Strategy -- Regex-based pattern matching per transaction type -- Keyword detection: "sent to", "paid to", "withdrawn.*from.*AGENT" (agent), "withdrawn.*from an ATM" (ATM), "deposited", "received", "airtime purchased", "bought.*airtime for", "M-PESA Global", "reversed", "Fuliza M-PESA amount", "paid to Fuliza", "Fuliza M-PESA of", "M-Shwari", "KCB M-Pesa" -- Amount extraction: `Ksh[\d,]+\.\d{2}` pattern -- Date extraction: `\d{1,2}/\d{1,2}/\d{2,4} at \d{1,2}:\d{2} [AP]M` -- International phone detection: `\+\d{7,15}` pattern (for M-Pesa Global) -- Country extraction: text in parentheses after international phone number -- Fallback: store unparsed M-Pesa SMS with type `UNKNOWN` for later parsing improvements - ---- - -## Database Schema (Room) - -### transactions -| Column | Type | Description | -|--------|------|-------------| -| id | Long (PK, auto) | Primary key | -| transactionCode | String (unique) | M-Pesa transaction code | -| type | Enum | SEND, BUY_GOODS, PAY_BILL, WITHDRAW_AGENT, WITHDRAW_ATM, DEPOSIT, RECEIVED, AIRTIME_SELF, AIRTIME_OTHER, MPESA_GLOBAL, FULIZA, FULIZA_REPAYMENT, FULIZA_REVERSAL, MSHWARI, KCB_MPESA, REVERSAL, UNKNOWN | -| amount | Double | Transaction amount in Ksh | -| transactionCost | Double | M-Pesa fee | -| recipientName | String? | Name of recipient/merchant | -| recipientPhone | String? | Phone number (if person) | -| accountNumber | String? | Account number (paybill) | -| destinationCountry | String? | Country name for M-Pesa Global transfers | -| balance | Double | M-Pesa balance after transaction | -| direction | Enum | INFLOW, OUTFLOW (derived at parse time — simplifies totals/chart queries) | -| categoryId | Long? (FK) | Assigned category | -| fulizaAmount | Double? | Fuliza amount (if applicable) | -| fulizaOutstanding | Double? | Fuliza outstanding (if applicable) | -| rawSms | String | Original SMS text (for re-parsing) | -| timestamp | Long | Transaction timestamp (epoch ms) | -| createdAt | Long | Record creation time | - -### categories -| Column | Type | Description | -|--------|------|-------------| -| id | Long (PK, auto) | Primary key | -| name | String | Category name | -| icon | String | Material icon name | -| color | String | Hex color code | -| isDefault | Boolean | System default vs user-created | - -### budgets -| Column | Type | Description | -|--------|------|-------------| -| id | Long (PK, auto) | Primary key | -| categoryId | Long? (FK) | Null = overall budget | -| monthlyLimit | Double | Budget limit in Ksh | -| isActive | Boolean | Whether alerts are enabled | - -### category_rules -| Column | Type | Description | -|--------|------|-------------| -| id | Long (PK, auto) | Primary key | -| categoryId | Long (FK) | Target category | -| matchType | Enum | RECIPIENT_NAME, PHONE, TILL, PAYBILL | -| matchValue | String | Value to match | - -### Default Categories -| Category | Icon | Color | Auto-match examples | -|----------|------|-------|---------------------| -| Groceries | shopping_cart | #4CAF50 | NAIVAS, QUICKMART, CARREFOUR | -| Transport | directions_car | #2196F3 | Uber, Bolt, matatu paybills | -| Bills & Utilities | receipt | #FF9800 | KPLC, NAIROBI WATER, DSTV | -| Airtime & Data | phone_android | #9C27B0 | Airtime purchases | -| Food & Dining | restaurant | #E91E63 | Restaurants, fast food | -| Send Money | person | #607D8B | Person-to-person transfers | -| Received | account_balance_wallet | #00BCD4 | Money received | -| Withdrawal | atm | #795548 | Agent withdrawals | -| Deposit | savings | #8BC34A | Cash deposits | -| Shopping | shopping_bag | #FF5722 | General merchants | -| International | public | #3F51B5 | M-Pesa Global transfers | -| Savings & Loans | account_balance | #009688 | M-Shwari, KCB M-Pesa | -| Other | category | #9E9E9E | Uncategorized | - ---- - -## Screens & UI - -### Design System -- **Color palette:** M-Pesa green (#4CAF50) as primary, warm neutrals, category-specific accent colors -- **Typography:** System font, scalable (Small/Medium/Large/Extra Large + System Default) -- **Cards:** Rounded corners (16dp), subtle elevation, clean spacing -- **Animations:** Smooth transitions between screens, animated chart loading -- **Dark mode:** Full dark theme, follows system by default with manual override - -### 1. Home Dashboard -``` -┌─────────────────────────────────┐ -│ Good morning 👋 │ -│ │ -│ M-PESA Balance │ -│ Ksh 12,500.00 │ -│ (from last transaction) │ -│ │ -│ ┌─────────────────────────┐ │ -│ │ March 2026 │ │ -│ │ Spent: Ksh 45,200 │ │ -│ │ ▼ 12% vs last month │ │ -│ │ Fees: Ksh 320 │ │ -│ └─────────────────────────┘ │ -│ │ -│ [Today] [This Week] [Month] │ -│ │ -│ ┌─────────────────────────┐ │ -│ │ 🍩 Donut Chart │ │ -│ │ Category Breakdown │ │ -│ └─────────────────────────┘ │ -│ │ -│ Recent Transactions │ -│ ┌─────────────────────────┐ │ -│ │ 🛒 Naivas -Ksh 1,200 │ │ -│ │ 📱 Airtime -Ksh 100 │ │ -│ │ 💰 Received +Ksh 5,000 │ │ -│ └─────────────────────────┘ │ -│ │ -│ ┌────┐┌────┐┌────┐┌────┐ │ -│ │Home││List││Stats││ ⚙️ │ │ -│ └────┘└────┘└────┘└────┘ │ -└─────────────────────────────────┘ -``` - -> **Balance display:** Shows the `balance` field from the most recent transaction. This is the last known M-Pesa balance — not a live query. Labeled "from last transaction" so users don't confuse it with a real-time check. - -### 2. Transactions List -- Grouped by day with day totals -- Each item: category icon + recipient/merchant + amount (red spent, green received) -- Search bar at top -- Filter chips: All, Sent, Received, Bills, Goods, Withdraw -- Tap → transaction detail bottom sheet -- Long press → change category - -### 3. Trends / Analytics -- Bar chart: daily spending for current month -- Line chart: monthly spending over time -- Top 5 recipients/merchants list -- Category comparison vs previous period -- **Transaction fees summary** — total fees paid this period (most people don't realize how much they spend on M-Pesa fees) -- Period selector: 7D / 30D / 90D / 1Y - -### 4. Settings -``` -Appearance - ├── Theme: System / Light / Dark - └── Font Size: System Default / Small / Medium / Large / Extra Large - -Notifications - ├── Daily Summary: ON/OFF + time picker - ├── Weekly Summary: ON/OFF - ├── Budget Alerts: ON/OFF - └── Large Transaction Alert: ON/OFF + threshold (Ksh) - -Budgets - ├── Overall Monthly Budget: Ksh ___ - └── Per Category: [list of categories with limits] - -Data - ├── Import SMS History - ├── Export Data (CSV/JSON) - ├── Unparsed Messages (X) — view SMS that failed to parse - └── Clear All Data (with confirmation) - -Backup - ├── Auto Backup: ON/OFF - ├── Backup Frequency: Daily / Weekly - ├── Google Account: shown - ├── Last Backup: date - ├── Back Up Now - └── Restore from Backup - -About - ├── Version: 1.0.0 - ├── Check for Updates - └── What's New -``` - ---- - -## Notification System - -| Notification | Trigger | Default | -|-------------|---------|---------| -| Daily Summary | Scheduled (user picks time, default 8 PM) | ON | -| Weekly Summary | Every Sunday evening | ON | -| Budget Warning | 80% of budget reached | ON | -| Budget Exceeded | 100% of budget reached | ON | -| Large Transaction | Single transaction > threshold | OFF (default Ksh 5,000) | - -Implementation: -- WorkManager for periodic summary jobs -- Immediate check on each new transaction for budget/large amount alerts -- NotificationChannel: "Spending Summaries", "Budget Alerts" - ---- - -## Backup System - -### Google Drive Auto-Backup -- Uses Google Sign-In (already on every Android phone) -- Backs up Room database + settings to app-specific Google Drive folder -- No extra permissions needed (app data folder is automatic) -- Frequency: daily or weekly (user choice) -- On fresh install: detect existing backup → prompt restore - -### Manual Export/Import -- Export: generates a ZIP file containing: - - `transactions.csv` — human-readable, can open in Excel - - `data.json` — full database export for app restore -- Import: select ZIP file → restore all data -- Share via WhatsApp, Bluetooth, email, etc. - -### SMS Re-import (Fallback) -- Scan all existing SMS from "MPESA" sender -- Parse and import, skipping duplicates (by transaction code) -- Progress indicator during import - ---- - -## Self-Update System - -### Flow -1. App checks GitHub Releases API on launch (max once per day) -2. Compares current version with latest release tag -3. If newer version exists: - - Shows non-intrusive banner: "Update available (v1.2.0)" - - Tap → shows changelog (from release notes) - - "Download & Install" button -4. Downloads APK to app cache -5. Triggers Android package installer intent -6. User confirms install (standard Android prompt) - -### GitHub Release Format -``` -Tag: v1.0.0 -Title: Version 1.0.0 -Body: Changelog in markdown -Asset: ledga-v1.0.0.apk -``` - -### Endpoint -``` -GET https://api.github.com/repos/{owner}/{repo}/releases/latest -``` - ---- - -## Auto-Categorization - -### Rule Engine (Priority Order) -1. **User rules** — if user manually categorized a merchant before, remember it -2. **Category rules table** — pattern matching on recipient name, till number, paybill -3. **Transaction type fallback** — airtime → Airtime, withdrawal → Withdrawal, etc. -4. **Default** — "Other" category - -### Pre-loaded Rules -| Pattern | Match Type | Category | -|---------|------------|----------| -| NAIVAS, QUICKMART, CARREFOUR, CLEANSHELF | Recipient | Groceries | -| KPLC, KENYA POWER | Recipient | Bills | -| 888880 (KPLC Prepaid), 888888 (KPLC Postpaid) | Paybill | Bills | -| NAIROBI WATER, ELDORET WATER | Recipient | Bills | -| 444400 (Nairobi Water) | Paybill | Bills | -| DSTV, GOTV, SHOWMAX | Recipient | Bills | -| UBER, BOLT, LITTLE | Recipient | Transport | -| JAVA, KFC, CHICKEN INN, PIZZA INN | Recipient | Food & Dining | -| (any self airtime) | Type | Airtime | -| (any airtime for others) | Type | Airtime | -| (any withdrawal) | Type | Withdrawal | -| (any deposit) | Type | Deposit | -| (any received) | Type | Received | -| (any send money) | Type | Send Money | -| (any M-Pesa Global) | Type | International | -| (any M-Shwari) | Type | Savings & Loans | -| (any KCB M-Pesa) | Type | Savings & Loans | - -Users can add/edit rules from the category settings. - ---- - -## Permissions Required - -| Permission | Purpose | When Requested | -|-----------|---------|----------------| -| RECEIVE_SMS | Listen for new M-Pesa SMS in real-time | On first launch | -| READ_SMS | Import existing M-Pesa SMS history | When user taps "Import History" | -| POST_NOTIFICATIONS | Daily summaries, budget alerts | On first launch (Android 13+) | -| INTERNET | Google Drive backup, update checks | Automatic | -| REQUEST_INSTALL_PACKAGES | Self-update APK installation | When user taps "Install Update" | - ---- - -## Project Structure - -``` -ledga/ -├── app/ -│ ├── src/ -│ │ ├── main/ -│ │ │ ├── java/com/ledga/app/ -│ │ │ │ ├── MainActivity.kt -│ │ │ │ ├── LedgaApp.kt -│ │ │ │ ├── di/ -│ │ │ │ │ ├── AppModule.kt -│ │ │ │ │ └── DatabaseModule.kt -│ │ │ │ ├── data/ -│ │ │ │ │ ├── db/ -│ │ │ │ │ │ ├── AppDatabase.kt -│ │ │ │ │ │ ├── Migrations.kt -│ │ │ │ │ │ ├── dao/ -│ │ │ │ │ │ │ ├── TransactionDao.kt -│ │ │ │ │ │ │ ├── CategoryDao.kt -│ │ │ │ │ │ │ └── BudgetDao.kt -│ │ │ │ │ │ └── entity/ -│ │ │ │ │ │ ├── Transaction.kt -│ │ │ │ │ │ ├── Category.kt -│ │ │ │ │ │ ├── Budget.kt -│ │ │ │ │ │ └── CategoryRule.kt -│ │ │ │ │ ├── repository/ -│ │ │ │ │ │ ├── TransactionRepository.kt -│ │ │ │ │ │ ├── CategoryRepository.kt -│ │ │ │ │ │ └── SettingsRepository.kt -│ │ │ │ │ └── parser/ -│ │ │ │ │ └── MpesaSmsParser.kt -│ │ │ │ ├── receiver/ -│ │ │ │ │ └── SmsReceiver.kt -│ │ │ │ ├── worker/ -│ │ │ │ │ ├── NotificationWorker.kt -│ │ │ │ │ ├── BackupWorker.kt -│ │ │ │ │ └── UpdateCheckWorker.kt -│ │ │ │ ├── ui/ -│ │ │ │ │ ├── theme/ -│ │ │ │ │ │ ├── Theme.kt -│ │ │ │ │ │ ├── Color.kt -│ │ │ │ │ │ └── Type.kt -│ │ │ │ │ ├── navigation/ -│ │ │ │ │ │ └── AppNavigation.kt -│ │ │ │ │ ├── onboarding/ -│ │ │ │ │ │ ├── OnboardingScreen.kt -│ │ │ │ │ │ └── OnboardingViewModel.kt -│ │ │ │ │ ├── home/ -│ │ │ │ │ │ ├── HomeScreen.kt -│ │ │ │ │ │ └── HomeViewModel.kt -│ │ │ │ │ ├── transactions/ -│ │ │ │ │ │ ├── TransactionsScreen.kt -│ │ │ │ │ │ ├── TransactionDetailSheet.kt -│ │ │ │ │ │ └── TransactionsViewModel.kt -│ │ │ │ │ ├── trends/ -│ │ │ │ │ │ ├── TrendsScreen.kt -│ │ │ │ │ │ └── TrendsViewModel.kt -│ │ │ │ │ ├── settings/ -│ │ │ │ │ │ ├── SettingsScreen.kt -│ │ │ │ │ │ ├── SettingsViewModel.kt -│ │ │ │ │ │ └── UnparsedSmsScreen.kt -│ │ │ │ │ └── components/ -│ │ │ │ │ ├── TransactionCard.kt -│ │ │ │ │ ├── CategoryChip.kt -│ │ │ │ │ ├── SpendingChart.kt -│ │ │ │ │ └── BudgetProgressBar.kt -│ │ │ │ └── util/ -│ │ │ │ ├── CurrencyFormatter.kt -│ │ │ │ └── DateUtils.kt -│ │ │ ├── res/ -│ │ │ └── AndroidManifest.xml -│ │ └── test/ -│ │ └── java/com/ledga/app/ -│ │ └── parser/ -│ │ └── MpesaSmsParserTest.kt -│ └── build.gradle.kts -├── build.gradle.kts -├── settings.gradle.kts -└── README.md -``` - ---- - -## Build Phases - -### Phase 1 — Core (MVP) -- [ ] Project setup (Kotlin, Compose, Room, Hilt, Gradle) -- [ ] SMS parser — handle all M-Pesa transaction types (including M-Pesa Global, M-Shwari, KCB M-Pesa) -- [ ] SMS parser unit tests — real SMS samples for every transaction type -- [ ] Room database — transactions, categories, category rules, migrations setup -- [ ] SMS BroadcastReceiver — real-time capture -- [ ] SMS history import -- [ ] Onboarding flow — welcome screen, SMS permission with plain-language explanation, optional history import -- [ ] Home dashboard — current balance, spending total, fees total, donut chart, recent transactions -- [ ] Transactions list — grouped by day, search, filter -- [ ] Basic auto-categorization with default rules (name + paybill/till matching) -- [ ] Bottom navigation (Home, Transactions, Trends, Settings) -- [ ] Theme system (System/Light/Dark) - -### Phase 2 — Polish -- [ ] Trends screen — bar charts, line charts, top merchants, fees summary, period comparison -- [ ] Font size settings (System/Small/Medium/Large/Extra Large) -- [ ] Budget system — set limits, track progress bars -- [ ] Category management — add/edit/delete custom categories -- [ ] Transaction detail bottom sheet -- [ ] Long press to re-categorize -- [ ] Category rules editor -- [ ] Unparsed SMS viewer (Settings → Data) - -### Phase 3 — Notifications & Backup -- [ ] Daily spending summary notification -- [ ] Weekly summary notification -- [ ] Budget alert notifications -- [ ] Large transaction alerts -- [ ] Notification settings (toggles, time pickers) -- [ ] Google Drive auto-backup -- [ ] Google Drive restore on fresh install -- [ ] Manual export (CSV + JSON zip) -- [ ] Manual import - -### Phase 4 — Distribution -- [ ] APK signing setup — generate keystore, document secure storage -- [ ] GitHub Actions CI/CD — build release APK on push to main -- [ ] GitHub Releases self-update system -- [ ] Update check on launch -- [ ] Changelog display -- [ ] APK download + install flow -- [ ] App icon and splash screen -- [ ] Final UI polish and animations - ---- - -## App Name - -**Ledga** — a play on "ledger". Short, unique, easy to say. -- Package name: `com.ledga.app` - ---- - -## Onboarding Flow - -First launch experience for non-technical users. Only shown once — a `hasCompletedOnboarding` flag in DataStore skips it on subsequent launches. - -``` -┌─────────────────────────────────┐ -│ │ -│ Welcome to Ledga │ -│ │ -│ Track your M-Pesa spending │ -│ automatically. No typing, │ -│ no manual entry. │ -│ │ -│ [Get Started →] │ -│ │ -└─────────────────────────────────┘ - ↓ -┌─────────────────────────────────┐ -│ │ -│ Ledga reads your M-Pesa SMS │ -│ to track your spending. │ -│ │ -│ Your messages never leave │ -│ your phone. Everything stays │ -│ private and offline. │ -│ │ -│ [Allow SMS Access →] │ -│ │ -└─────────────────────────────────┘ - ↓ -┌─────────────────────────────────┐ -│ │ -│ Import your M-Pesa history? │ -│ │ -│ We can scan your existing │ -│ M-Pesa messages to build │ -│ your spending history. │ -│ │ -│ [Import History] │ -│ [Skip — start fresh] │ -│ │ -│ ┌─────────────────────┐ │ -│ │ ████████░░ 67% │ │ -│ │ Found 342 transactions│ │ -│ └─────────────────────┘ │ -│ │ -└─────────────────────────────────┘ - ↓ -┌─────────────────────────────────┐ -│ │ -│ You're all set! │ -│ │ -│ Ledga will now track your │ -│ M-Pesa spending in the │ -│ background. │ -│ │ -│ [Go to Dashboard →] │ -│ │ -└─────────────────────────────────┘ -``` - ---- - -## Testing Strategy - -### Unit Tests (Critical) -- **SMS Parser** — test every transaction type with real M-Pesa SMS samples - - At least 3 variations per type (different amounts, names, edge cases) - - Malformed messages (truncated SMS, missing fields) - - Messages from non-M-Pesa senders (should be ignored) - - Fuliza messages embedded in send/buy goods SMS - - M-Pesa Global with various country formats - - M-Shwari/KCB M-Pesa detection - - Amount parsing edge cases: `Ksh1.00`, `Ksh100,000.00` -- **Auto-categorization** — rule matching priority, paybill matching, fallback behavior -- **CurrencyFormatter** — Ksh formatting, large numbers, zero amounts - -### Integration Tests -- Room database — DAO queries, migrations, duplicate transaction code handling -- SMS import — bulk parsing performance, duplicate skipping - -### Manual Testing -- Collect real M-Pesa SMS from family members (anonymized) as test fixtures -- Test on low-end Android devices (target audience likely has budget phones) - ---- - -## APK Signing & CI/CD - -### Keystore -- Path: `mobile/ledga/keystore/ledga-release.jks` (gitignored) -- Key alias: `ledga` -- Generate a release keystore and store it securely -- **CRITICAL:** Back up the keystore — if lost, existing installs can never receive updates (Android requires consistent signing). The VPS backup job does not cover this file; back it up manually to a password manager or offline storage. -- Store keystore password and key alias in a secure location (not in the repo) - -### Build Commands -```bash -cd ~/dev/mobile/ledga - -# Debug APK -./gradlew assembleDebug -# -> app/build/outputs/apk/debug/app-debug.apk - -# Release APK (signed, requires keystore + secrets) -./gradlew assembleRelease -# -> app/build/outputs/apk/release/app-release.apk - -# Tests -./gradlew testDebugUnitTest -``` - -### Release a Version -```bash -git tag v1.0.0 && git push origin v1.0.0 -# GitHub Action builds signed APK and creates release automatically -``` - -### GitHub Actions -```yaml -# .github/workflows/release.yml -# Triggered on push to main or manual dispatch -# Steps: -# 1. Checkout code -# 2. Set up JDK 17 -# 3. Decode keystore from GitHub secret -# 4. Build release APK (assembleRelease) -# 5. Sign APK -# 6. Create GitHub Release with APK asset -``` - -### GitHub Secrets Required -| Secret | Purpose | -|--------|---------| -| `KEYSTORE_BASE64` | Base64-encoded release keystore | -| `KEYSTORE_PASSWORD` | Keystore password | -| `KEY_ALIAS` | Key alias name | -| `KEY_PASSWORD` | Key password | - ---- - -## Database Migrations - -Schema changes are inevitable as the app evolves. Room requires explicit migrations to preserve user data. - -### Strategy -- **Never use `fallbackToDestructiveMigration()`** — this destroys all user data on schema change -- Define all migrations in `Migrations.kt` as `Migration(oldVersion, newVersion)` objects -- Test every migration path with Room's `MigrationTestHelper` -- Keep a version history: - -| Version | Changes | -|---------|---------| -| 1 | Initial schema (transactions, categories, budgets, category_rules) | - -### Example Migration Pattern -```kotlin -val MIGRATION_1_2 = object : Migration(1, 2) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE transactions ADD COLUMN newField TEXT") - } -} -``` - ---- - -## Notes -- All data stays on device — no external server, no analytics, no tracking -- SMS parsing must be thoroughly tested with real M-Pesa SMS variations -- Fuliza messages have extra fields — parser must handle gracefully -- Fuliza has 3 distinct message types: borrow (Fuliza M-PESA amount), repayment (paid to Fuliza), and reversal — each has a different format -- Some M-Pesa SMS formats may vary slightly — keep raw SMS stored for re-parsing -- Transaction code uniqueness prevents duplicate imports -- Multi-SIM: some users have two M-Pesa lines — consider adding a `simSlot` or `phoneNumber` field on transactions (extracted from SMS subscription ID) if multi-line tracking is needed later -- Google Drive backup uses `appDataFolder` scope — backups are invisible to the user in Drive. This is by design (no extra permissions), but users should know they won't find the file browsing Drive manually -- The `direction` column (INFLOW/OUTFLOW) is set at parse time based on transaction type, avoiding repeated derivation in every query for totals and charts