|
Some checks failed
CI / build (push) Has been cancelled
Alan skipped the purge after the token refused it. The old image URLs age out of the edge cache; no page links to them. |
||
|---|---|---|
| .forgejo/workflows | ||
| .github/workflows | ||
| app | ||
| design | ||
| design-system | ||
| docs | ||
| gradle/wrapper | ||
| store-assets | ||
| tools | ||
| website | ||
| .cgcignore | ||
| .editorconfig | ||
| .git-blame-ignore-revs | ||
| .gitignore | ||
| AGENTS.md | ||
| build.gradle.kts | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| DESIGN-3.1.md | ||
| DESIGN-3.4.md | ||
| DESIGN-3.md | ||
| DESIGN.md | ||
| GEMINI.md | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| LICENSE | ||
| README.md | ||
| ROADMAP.md | ||
| settings.gradle.kts | ||
| TODOS.md | ||
Debt Tracker
A native Android app for tracking shared expenses and debts between people. Built with Kotlin and Jetpack Compose.
Fully offline, no accounts, no ads, no tracking.
v3.4.3 (2026-09-16) — The app rotates: the portrait lock the Play Console flagged for tablets and foldables is gone, a turn keeps whatever you were typing, and the Settings screen no longer sits under the camera cutout in landscape. Live on Google Play at 100% since 2026-09-16 as versionCode 24. 1107 tests. See
CHANGELOG.md.v3.4.2 (2026-09-16) — A fix under the hood for the Play Console's edge-to-edge items: the app no longer calls the Android APIs Google deprecated for drawing behind the system bars, and the bar icons still follow the theme on every supported Android version. Nothing on screen changes. Was live on Google Play at 100% on 2026-09-16 as versionCode 23 until 3.4.3 replaced it the same day. 1104 tests. See
CHANGELOG.md.v3.4.1 (2026-09-04) — The pay-off screen's spend bars show each member's share in grey, what is still owed in red and what is owed back in green, on every theme. The six-month chart on Totals shows its scale, and the person balance chart draws a zero line. Live on Google Play at 100% since 2026-09-04 as versionCode 22. 1087 tests. See
CHANGELOG.md.
v3.4.0 (2026-08-31) — Shared groups without a server: send a group as a
.dtgroup.jsonfile, everyone logs on their own phone, and the app merges it back in any order; a group you already have adopts the file instead of doubling up. The one Play update carrying both this and 3.3.0, live at 100%. Database 6 → 7, backups at format 4.0; the system's own Auto Backup is off so a reinstall starts clean, while your folder backups still work. 1049 tests. SeeCHANGELOG.md.v3.3.0 (2026-08-30) — Balances open with what the group has spent so far: the total, the number of expenses, the average per person, and each member's share beside what they covered. The group screen links there with a "spent so far" line. Cut and tagged locally; shipped to Play folded into v3.4.0. 884 tests. See
CHANGELOG.md.
v3.2.0 (2026-08-24) — live on Google Play at 100%: six new themes for 18 in all, six per group; 34 currencies with a search box, no decimals for yen, won and đồng, and the group currency picked from the same list; Undo confirms in the top snackbar; the amount keypad stops at ten whole digits; three fixes from the on-device pass (F13–F15). Under the hood: ktfmt + ktlint over the whole tree and in the gate, Gradle 9.7 / AGP 9.3 / Kotlin 2.4 / Paparazzi 2, JDK 21 to build. 858 tests. See
CHANGELOG.md.v3.1.3 live on Google Play (2026-08-24, 100% production) — recorded group payments can be edited or deleted (tap the row in the group's list); on a person's screen a tap opens Edit / Delete; the amount keypad, the balance chart and the split editor show the group or default currency. 1490 tests. See
CHANGELOG.md.v3.1.2 (2026-08-24) — a crash fix: entering an amount of $2,000,000 or more in Add debt / Add payment no longer closes the app. Built on v3.1.1, which targets Android 16 (API 36) for Play's 2026-08-31 requirement. See
CHANGELOG.md.v3.0.0 (2026-06-12) — full Splitwise feature parity, kept entirely local: groups with five split modes and multiple payers, fewest-payments settlement, categories, an activity feed, search, monthly totals, receipts, and comments — plus a four-tab shell and an entry-flow redesign from real user feedback. 1324 tests (unit, snapshot, accessibility). See
docs/ROADMAP_3.0.mdandDESIGN-3.md.v2.0.0 — the visual foundation: 20-component design system, 12 curated themes, WCAG AA contrast, reduced motion support. See
DESIGN.md.
Why this exists
Splitwise — long the default for splitting bills with friends — added a 3-5 expense/day cap to its free tier in late 2024, with a $40/year Pro tier to remove it. Debt Tracker is for people who want to do the same job without an account, without ads, without a daily limit, and without a paywalled feature list.
| Splitwise (free) | Debt Tracker | |
|---|---|---|
| Daily expense limit | 3-5/day | Unlimited |
| Account required | Yes | No |
| Ads | Yes | Never |
| Pro tier | $40/year | $0 forever |
| Works offline | No | Yes |
| Open source | No | GPL-3.0 |
| Cross-user cloud sync | Yes | No (by design) |
| Share a group across phones | Via their servers | A file you send, merged locally |
Honest comparison including where Splitwise wins: vs Splitwise (the page source; the GitHub Pages copy went with the deleted repo).
Features
- Track People — Add anyone you share expenses with
- Log Transactions — Record who owes whom, with descriptions and dates
- Automatic Balances — See at a glance who owes you and who you owe
- Group totals — Balances show the group's total spend, the average per person, and each member's share beside what they covered
- Shared Groups — Send a group as a
.dtgroup.jsonfile; everyone keeps their own copy and the app merges what each person adds, in any order, with no account and no server. A group you already have adopts the file instead of doubling up. - Recurring Charges — Set up automatic monthly expenses (rent, subscriptions, etc.) with immediate first fire, full editing (amount, description, type, frequency, next-due date), on-demand "charge now" trigger, and 12-month backfill limit
- Transaction History — View, edit, or delete past transactions; recorded group payments can be edited or deleted from the group's list
- Backup & Restore — Export/import data as JSON files
- Share Backups — Send backup files via email, cloud storage, or messaging apps
- Automatic Backups — Schedule daily, weekly, or monthly backups. Stored outside the app via SAF so they survive uninstall. Snapper-style retention (3 daily, 2 weekly, 1 monthly).
- Backup Browser — Browse and restore from automatic backups with human-readable dates, retention badges, and entity counts
- Colour Themes — 18 themes across Everyday, Mood, and Terminal groups (12 in v2.0, six more in v3.2.0). Theme choice persists across app restarts.
Privacy
All data stays on your device. No internet connection needed, no accounts required, no analytics or tracking. The app never uploads your financial information. Sharing a group is deliberate: the app writes a file and you choose how it travels, with no server in the middle.
Tech Stack
- Language: Kotlin
- UI: Jetpack Compose with Material3
- Architecture: MVVM with ViewModel + Repository
- Database: Room (SQLite) with atomic transactions
- Async: Kotlin Coroutines & Flow
- Serialization: Gson
- Screenshot Testing: Paparazzi (
:design-systemmodule) - Min SDK: 26 (Android 8.0)
- Target SDK: 36 (Android 16)
Building
Requirements
- JDK 21 (Paparazzi 2 needs it; point
org.gradle.java.homeat it in~/.gradle/gradle.propertiesif your default is older) - Android SDK platform 36 (both modules); the Gradle plugin installs build-tools on demand
Debug Build
./gradlew assembleDebug
APK output: app/build/outputs/apk/debug/app-debug.apk
Release Build
Requires keystore.properties in the project root with signing config.
./gradlew bundleRelease
AAB output: app/build/outputs/bundle/release/app-release.aab
Testing
# Unit tests
./gradlew test
# Record Paparazzi screenshot goldens (after visual changes)
./gradlew :design-system:recordPaparazziDebug
# Verify screenshots match goldens (pre-merge)
./gradlew :design-system:verifyPaparazziDebug
Golden images are committed at design-system/src/test/snapshots/images/. The verify task fails if any component renders differently from its golden — run recordPaparazziDebug to update after intentional visual changes.
Project Structure
design-system/src/main/java/com/debttracker/ds/
├── DtTheme.kt # Entry point composable
├── DtThemeId.kt # 18-theme enum + mode/group flags
├── ThemeResolver.kt # DtThemeId → ResolvedTheme (colours, typo, shape)
├── tokens/
│ ├── DtColors.kt # Semantic colour tokens
│ ├── DtMotion.kt # Animation timing tokens
│ ├── DtSpacing.kt # 4dp grid spacing scale
│ ├── DtShape.kt # Corner radius tokens (xs/sm/md/lg/full/pill)
│ └── DtTypography.kt # Type scale + font families
├── themes/
│ ├── ThemePalette.kt # Shared palette type
│ └── (18 files) # One palette per theme (Paper★, Midnight, Sage, …)
├── components/
│ ├── DtButton.kt # 5 variants, 3 sizes, loading state
│ ├── DtIconButton.kt # Icon-only button with danger toggle
│ ├── DtCard.kt # Elevated card with hero variant
│ ├── DtListRow.kt # M3 ListItem wrapper
│ ├── DtTopBar.kt # Top app bar with scroll behaviour
│ ├── DtFab.kt # FAB + extended FAB
│ ├── DtAvatar.kt # Deterministic colour avatar
│ ├── DtPill.kt # 4-intent status pill
│ ├── DtBadge.kt # Compact inline badge
│ ├── DtTextField.kt # Outlined text input with error slot
│ ├── DtAmountField.kt # Hero numeric input (no system IME)
│ ├── DtNumericKeypad.kt # 4×3 keypad with TalkBack labels
│ ├── DtDateField.kt # Tap-to-open M3 DatePicker + presets
│ ├── DtChip.kt # Selectable filter chip (token-aware)
│ ├── DtConfetti.kt # Canvas-based celebration particles
│ ├── DtDialog.kt # M3 dialog wrapper + DtAction
│ ├── DtBottomSheet.kt # M3 ModalBottomSheet wrapper
│ ├── DtEmojiChip.kt # 28dp emoji circle (transaction type icons)
│ ├── DtEmptyState.kt # Centred no-data state with CTA
│ ├── DtSkeleton.kt # Rounded-rect shimmer placeholder
│ └── DtSnackbar.kt # Undo-capable snackbar with accent strip
├── util/
│ ├── DtCurrencies.kt # 34-currency catalog: prefix, code, name, minor digits, words
│ ├── DtFormatAmount.kt # Single-source currency formatter
│ ├── DtFormatAmountWords.kt # TalkBack-friendly amount-to-words
│ └── DtReducedMotion.kt # System reduced motion detection
└── preview/
├── DtThemePreview.kt # Token showcase preview
└── ComponentPreviews.kt # All components × 18 themes
app/src/main/java/com/debttracker/app/
├── MainActivity.kt
├── DebtTrackerApplication.kt # Application: owns the singleton DB + repository
├── data/
│ ├── backup/
│ │ ├── AutoBackupManager.kt # Backup scheduling & preferences
│ │ ├── AutoBackupWorker.kt # WorkManager background worker
│ │ └── BackupRetention.kt # Snapper-style retention algorithm
│ ├── database/
│ │ ├── DebtTrackerDao.kt # Room DAOs
│ │ └── DebtTrackerDatabase.kt # Room database
│ ├── model/
│ │ ├── Person.kt # Person entity
│ │ ├── Transaction.kt # Transaction entity
│ │ ├── RecurringCharge.kt # Recurring charge entity
│ │ ├── PersonLastActivity.kt # DAO query result for recency grouping
│ │ └── BackupData.kt # Backup/restore data class
│ └── repository/
│ └── DebtRepository.kt # Repository layer
└── ui/
├── AppStartupViewModel.kt # Launch-time recurring processing + backup
├── HomeViewModel.kt # Home: persons, recency, search, sticky pre-fill
├── PersonViewModel.kt # Person detail (keyed per personId)
├── RecurringViewModel.kt # Recurring charges (keyed per personId)
├── SettingsViewModel.kt # Backup export / restore / clear-all
├── BackupBrowserViewModel.kt # SAF backup-folder browse + restore
├── SnackbarEvent.kt # Undo-carrying snackbar events
├── navigation/
│ └── AppNavState.kt # Typed back-stack holder (replaced enum Screen)
├── components/
│ └── PersonPicker.kt # Domain-coupled avatar row + "Add new"
└── screens/
├── AddExpenseSheet.kt # v2.0: bottom sheet with amount, person, type, date
├── BackupBrowserScreen.kt # Browse & restore backups
├── Dialogs.kt
├── HomeScreen.kt # v2.0: hero card, recency groups, search, FAB
├── HomeScreenHelpers.kt # Pure functions for balance/recency/search logic
├── TransactionHistoryScreen.kt # v2.0: person header, sparkline, time-grouped txns
├── PersonScreenHelpers.kt # Pure functions for time grouping, sparkline, labels
├── RecurringChargesScreen.kt
├── SettingsScreen.kt
└── SettleUpDialog.kt # v2.0: settle-up confirmation with formatted balance