29 Commits
Author SHA1 Message Date
ngenohkevin 42236c88e4 v1.6.0: car expense tracking (Fuel/Service) + tappable notifications
Mark any transaction as Fuel or Car Service from the detail sheet (a tag
separate from its category). New 'Car expenses' screen (You tab) shows the
car's lifetime cost plus per-tag This Week / This Month / All Time, summed
across every SIM. DB v4->v5 adds a nullable carTag column (additive, no
rebuild).

Also: notifications now carry a tap intent, so tapping opens the app and
clears them instead of lingering until manually swiped.
2026-06-28 16:49:58 +03:00
ngenohkevin 9d7fc230f4 v1.5.2: make Home period selector scrollable so 3 Months chip is reachable 2026-06-13 11:02:16 +03:00
ngenohkevin 0a1cadff57 v1.5.1: add 3 Months period to Home spending filter 2026-06-13 10:55:15 +03:00
ngenohkevin ec26507476 v1.5.0: multi-SIM detection + self-healing data accuracy
SIM detection: history import now attributes per-message sub_id (sim_id
fallback), receiver falls back to the sole active SIM when the OEM omits
the subscription extra, accounts re-link by phone number on SIM/slot
changes, and READ_PHONE_STATE is requested so lines get carrier names.

Accuracy: KCB withdrawals stored the savings-pocket balance as the wallet
balance and INFLOWs as OUTFLOW (goal contributions counted withdrawals).
Wallet-balance regex now wins and 'from your KCB/M-Shwari' parses as
inflow. Combined view sums per-line balances and Fuliza facts instead of
mixing lines; insights run account-agnostic for deterministic output.

Self-healing on launch: SIM sync, incremental catch-up SMS import (covers
receiver kills by OEM battery savers), one-time unassigned-only SIM
backfill, and a versioned re-parse that heals stored rows after parser
fixes while preserving user-set fields.
2026-06-10 09:07:10 +03:00
ngenohkevin d7ab85f97d v1.4.7: People view (sent/received) + surface Large filter
- New 'People' segment on Activity: browse who you've sent money to or
  received from, aggregated by name with totals and counts. Search by name,
  filter by a minimum total with a slider, toggle Sent/Received, and tap a
  person to see every transaction with them.
- Moved the 'Large' filter chip to the front of the row so it's visible
  without scrolling (it was the last, off-screen chip).
2026-06-08 10:46:20 +03:00
ngenohkevin 57cce3a72c ci: retry Gradle wrapper download to tolerate flaky distribution CDN
services.gradle.org intermittently returns 504 / times out while the
wrapper fetches the distribution, failing releases and emailing on every
blip. Warm the wrapper with a backoff retry before the real build/test
steps in both jobs.
2026-06-08 09:47:05 +03:00
ngenohkevin 13a92d18f2 v1.4.6: large-transactions slider, heatmap fix, insight contrast
- New 'Large' tab on Transactions with a slider (Ksh 1K–100K) to set the
  threshold live — drag for bigger/smaller. Shares the large-transaction
  alert threshold, so it's now adjustable from the UI for the first time.
- Spending heatmap was blank for non-UTC users: getDailySpending bucketed
  by UTC midnight while the grid looked up local-midnight keys (3h off in
  EAT → every cell zero). Now buckets by local day. Also: cells size to fit
  so the 24-week year view no longer overflows, and the legend shows the
  real 5-step alpha ramp.
- Insights 'All clear' empty-state body text was onSurface on a light card
  (invisible in dark mode) — now derives ink from the card like the rest.
2026-06-08 09:40:41 +03:00
ngenohkevin 0a80be72a2 v1.4.5: fix invisible text on tonal cards in dark mode
Insight cards (and the Goals/Update/Backfill tonal cards) have a fixed
light pastel background in both themes, but their content text used the
theme's onSurface — near-white in dark mode — so headlines vanished into
the light card. BentoCard already derived contrasting title/overline ink
via onTonal; the content lambdas now do the same for body, headline, and
actions.
2026-06-08 09:17:25 +03:00
ngenohkevin 20fe7ba5fd v1.4.4: correct Fuliza available-to-borrow + real zero balance
Two display fixes:

1. 'Available to borrow' showed the raw ceiling (e.g. 18K) ignoring what's
   already drawn. M-PESA's 'Available Fuliza limit' is the headroom at that
   SMS, so ceiling = that reading + its outstanding (0 after a full clear);
   borrowable now = ceiling − current outstanding. Card also shows the limit.

2. Balance card had a 'balance > 0' filter that skipped genuine Ksh0.00
   readings and surfaced a stale positive balance instead. A real zero (spent
   down / running on Fuliza) now shows; only parser-default zeros on
   repayment/reversal/companion/unknown rows are skipped.
2026-06-08 08:52:54 +03:00
ngenohkevin 88f93520ec v1.4.3: merge split Fuliza SMS + import progress
M-PESA now splits a Fuliza-funded transaction into two SMS sharing one
transaction code: the payment and a Fuliza companion carrying the
outstanding/limit. The unique index on transactionCode silently dropped
the companion (OnConflictStrategy.IGNORE), so outstanding read 0 while
the user actually owed money. insertTransaction now merges a same-code
companion onto the payment row (either arrival order); reparse preserves
the merged Fuliza fields since the payment's own rawSms lacks them.

Also: parse the 'Access Fee charged' phrasing as the Fuliza fee, and add
a running spinner + 'Importing X of Y' progress to Import SMS history
(previously it ran with no feedback).
2026-06-08 08:19:47 +03:00
ngenohkevin 6f88574165 v1.4.2: show Fuliza reading date + stale-data hint
The Fuliza card showed a bare outstanding figure with no recency, so a
stale reading (e.g. an old full-clear at 0) looked current. It now shows
'as of <date>' and, when the latest Fuliza SMS is over a day old, prompts
re-running Import SMS history — Safaricom moved Fuliza to a dedicated
sender, so older messages need a one-time re-import to be read in.
2026-06-08 07:54:01 +03:00
ngenohkevin 4e58bd8a8b v1.4.1: own-account transfers + Fuliza limit tracking
Marking a recipient as 'my own account' reclassifies its transactions
into a new transfer category excluded from all spending figures —
bank/card top-ups are moved money, not spending.

Fuliza coverage: accept the dedicated FULIZA sender id in the receiver
and both importers (borrows were going untracked while repayments were
captured), capture the available Fuliza limit from any SMS carrying it,
record borrow interest as a fee, and surface an outstanding/available
card on Home.

DB v3->v4: additive ALTER TABLEs only (categories.isTransfer,
transactions.fulizaLimit) + idempotent seed of the My Accounts category.
2026-06-07 15:57:40 +03:00
ngenohkevin 9600fa730c v1.4.0: true spending totals + monthly trends
Spending figures were inflated: Fuliza repayments double-counted the
already-counted borrow-backed purchase, M-Shwari/KCB deposits counted
own-money savings as spending, and reversed transactions never came
back out of the total. All spending queries now exclude these.

Also adds a month-by-month breakdown card to Trends (12 months,
delta vs previous month) and captures the credited amount on
standard-format reversal SMS instead of recording 0.0.
2026-06-07 15:24:59 +03:00
ngenohkevin f40a93e62a v1.3.6: auto-dismiss stale Fuliza insight + refresh after reparse
The Fuliza outstanding insight is stateful — it should disappear the
moment the underlying outstanding hits 0. Today it sticks until
manually dismissed, even after data updates.

InsightEngine now tracks a STATEFUL_KEYS set (just "fuliza:outstanding"
for now). If a rule that owns one of those keys returns no candidates
in this run, the row is deleted outright rather than being left around
as a tombstone.

SettingsViewModel.reparseAll() now also calls insightsRepository
.generateAll() once the row update completes. So the moment the user
finishes "Re-parse ALL transactions", any stale Fuliza alert reading
the limit-as-outstanding goes away, and any newly correct alerts show
up immediately.
2026-05-16 22:30:17 +03:00
ngenohkevin f83f644cd3 v1.3.5: reparse progress spinner + Today default on Home
- Re-parse ALL now shows a spinner + "Re-parsing all transactions…"
  while running, so taps don't feel ignored. Button is hidden during
  the work; reappears after the result line.
- Empty result (no transactions to re-parse) shows a hint pointing at
  "Import SMS history" instead of a confusing "0 of 0".
- Home period selector defaults to Today instead of Month — the spec
  showed Month first, but in practice users want to see today's
  spending at a glance and switch out only when needed.
2026-05-16 22:08:59 +03:00
ngenohkevin 6571cd0497 v1.3.4: fix Fuliza auto-pay balance + outstanding + reparse-all
Two parser bugs around Fuliza auto-pay that produced wrong balance and
phantom-outstanding alerts:

1. BALANCE_REGEX required the literal "Ksh" before the amount, but the
   full-clear auto-pay SMS ends "Your M-PESA balance is 8581.08." with
   no Ksh prefix. Balance was lost → stored as 0 → Home's
   getLatestTransactionWithBalance filter (balance > 0) fell back to a
   stale older transaction. Made the Ksh optional.

2. parseFulizaAutoPay was stuffing the *available Fuliza limit* into
   fulizaOutstanding. Those are opposites (available = ceiling - owed).
   After a full clear that meant FulizaRule saw the full 14k limit and
   raised "You owe Ksh 14,000" — incorrect. Now:
     - full clear  -> fulizaOutstanding = 0   (rule correctly bails)
     - partial     -> fulizaOutstanding = null (rule defers to the
                                                previous Fuliza tx;
                                                avoids reporting the
                                                available-limit value)

Reparse all transactions
- New "Re-parse ALL transactions" entry in You → Data. Walks every
  stored row through MpesaSmsParser and updates parser-derived fields
  in place, preserving user fields (categoryId, accountId, note, id).
- Needed because the broken regex above silently corrupted historical
  rows on import; this is the no-data-loss path back to correctness.
2026-05-16 21:56:10 +03:00
ngenohkevin c5be379c8e v1.3.3: donut chart circularity + period selector on Home
DonutChart was hardcoded to a 180dp Canvas. When placed in a 50/50 Row
inside the "Where it went" card on the S24 Ultra, the canvas overflowed
the parent column and clipped into an oval. Switched to
fillMaxWidth + aspectRatio(1f) + fillMaxSize Canvas so the donut always
stays circular and scales to whatever the parent gives it. Stroke width
also scales (18% of the box, capped at 32dp).

Home screen lost the Today / This Week / Month period selector in the
bento rewrite (HomeViewModel still exposed it, the UI just never
rendered it). Restored as a LedgaChip row above the hero card. The
hero overline now also tracks the selection ("SPENT TODAY" /
"SPENT THIS WEEK" / "SPENT THIS MONTH").
2026-05-16 21:28:37 +03:00
ngenohkevin 5ca2f89303 v1.3.2 hotfix: migration FK crash + dark-theme insight teaser
MIGRATION_1_2 was using ALTER TABLE to add transactions.accountId, but
SQLite can't add a FOREIGN KEY via ALTER. Room validates schema after
migration and threw IllegalStateException ("Migration didn't properly
handle: transactions") because the FK to mpesa_accounts was missing.
The whole DB stayed at v1, blocking every read/write — which is why
SMS import + SMS receive looked dead too.

Fix: standard SQLite table-swap dance. Create transactions_new with
both FKs declared, INSERT … SELECT old rows in, drop the old table,
rename. All indices recreated. Defensive DROP IF EXISTS in case a
prior failed run left junk.

BentoCard now picks title + overline colors from the tonal background
luminance instead of unconditionally using onSurface. On dark theme
the Insights teaser had near-white text on the (light-theme) pale
green bg — invisible. Now it adapts:
  - light tonal bg → dark ink
  - dark tonal bg → light ink
HomeScreen Insights teaser switched from hardcoded LedgaAccentSoft to
MaterialTheme.colorScheme.primaryContainer so the bg itself also
adapts to theme.
2026-05-16 13:55:31 +03:00
ngenohkevin 3dc9e0305e v1.3.1 hotfix: status-bar overlap + font fallback
- LedgaTopBar now applies WindowInsets.statusBars padding. Previously
  enableEdgeToEdge was on but the top bar drew at y=0, so the avatar
  and trailing icons sat under the system clock / WiFi indicator.
- PillTabBar uses WindowInsets.navigationBars padding instead of a
  hardcoded 21dp bottom so the floating pill sits above the system
  gesture pill on devices like the S24 Ultra.
- Typography switched from Google-Fonts Inter to FontFamily.SansSerif.
  Inter requires a downloadable-fonts round-trip via Google Play
  Services that doesn't always succeed; the fallback was hitting
  FontFamily.Default, which inherits the user's system font choice —
  on Samsung One UI that's frequently a handwritten / script style
  and broke the type rhythm everywhere. SansSerif uses the platform
  sans (One UI Sans on Galaxy, Roboto elsewhere) regardless of user
  preference.
2026-05-16 13:36:23 +03:00
ngenohkevin 85c3f167ab Untrack internal planning docs
LEDGA_REDESIGN.md and PROJECT_PLAN.md are kept locally for reference
but shouldn't live in the public repo.
2026-05-16 13:26:22 +03:00
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
ngenohkevin 00975aa7cc Fix update notification: add banner UI + reset rate limit on version change 2026-03-21 22:35:00 +03:00
ngenohkevin d29498edbb Add re-parse unparsed messages button
Settings > Data now has "Re-parse Unparsed Messages" button that
re-runs the fixed parser on all UNKNOWN transactions, properly
categorizing previously missed Fuliza auto-deductions, withdrawals,
airtime, and reversals.
2026-03-21 22:26:45 +03:00
ngenohkevin 729ef15cd3 Fix parser + redesign UI
Parser fixes (838 → ~0 unparsed):
- Fuliza auto-deduction (full/partial pay from balance)
- Withdraw alt format (Withdraw Ksh from agent)
- Reversal alt format (Reversal of transaction)
- Airtime self with lowercase confirmed
- Hustler Fund with 4-digit year dates
- Balance check messages filtered out
- SMS normalization (newlines, extra spaces)
- Amount regex handles Ksh with/without space

UI improvements:
- Category-specific Material icons (not generic cart)
- Colored icon backgrounds with rounded corners
- Green balance card with M-Pesa branding
- Category breakdown section (where your money went)
- Balance uses last non-zero transaction
- Better card shapes (20dp rounded)
- Font weight hierarchy
2026-03-21 22:11:36 +03:00
ngenohkevin 0f2ad432d6 Fix CI: add contents write permission for release creation 2026-03-21 21:35:06 +03:00
ngenohkevin e7120fe5ee Fix: remove default WorkManagerInitializer for Hilt custom init 2026-03-21 21:24:11 +03:00
ngenohkevin 706993e084 Fix CI: create keystore directory before decoding 2026-03-21 21:12:09 +03:00
ngenohkevin 047565500e Add README with features, tech stack, and usage docs 2026-03-21 21:11:26 +03:00
ngenohkevin 79741192b9 Initial release — Ledga M-Pesa spending tracker
Complete Android app (Kotlin/Jetpack Compose) with:
- SMS parser for all 17 M-Pesa transaction types
- Room database with auto-categorization engine
- Real-time SMS capture via BroadcastReceiver
- Onboarding flow with SMS history import
- Home dashboard with balance, spending, donut chart
- Transactions list with search, filters, day grouping
- Trends screen with Vico bar charts, top merchants
- Budget system with progress tracking
- Transaction detail sheet with re-categorization
- Notifications (daily/weekly summaries, budget alerts)
- Google Drive backup & restore
- Manual export/import (CSV + JSON ZIP)
- Self-update system via GitHub Releases API
- Theme system (System/Light/Dark) + font scaling
- Adaptive app icon with splash screen
- GitHub Actions CI/CD for release APK builds
2026-03-21 20:53:59 +03:00