feat(ui): Improve user experience, polish ui & add AI chat, Budgets#199
Conversation
Introduces a tonal palette (brand, brand-soft, income, expense, neutral, warm) wired in as theme extensions for light and dark, with a tighter shared radius scale and motion tokens. A new PageAppBar carries every content page with a context-aware leading slot: back arrow when there is a stack, brand mark that opens the drawer on root-level pages, search trigger that transforms the title into an inline field. Titles are screen-centered and rendered in brand green for presence. The bar shares the page surface with a hairline separator, so toolbar and body read as one continuous canvas. Drawer slims its rows, surfaces user identity in the header, and groups items by usage rhythm (Everyday, Organize, More) rather than alphabetic order. Empty states, tip banners, party cards, and the home wallet tile pick up the new tones and the Trakli warm accent appears in places that signal "you are here" or "this is a hint" without competing with the green primary.
… pattern All four entity surfaces follow one rhythm now: a slim summary chip up top, optional filter pills below, then a single rounded surface card holding compact tile rows with hairline dividers. Tapping a row opens a dedicated detail screen that shows hero + Received/Spent/Net totals + a six-month activity chart + the recent transactions for that entity. Edit and delete are demoted to the row overflow menu and the detail screen's app bar. Sorting now leads with whatever the user actually uses (most active first, default wallet pinned to the top), and search lives in the top bar via the search icon transforming the title into an inline input. Add screens (party, wallet, category, group) drop the old saturated green custom app bar in favour of the shared PageAppBar so they look like they belong to the same app.
The statistics tab leads with a compact recap teaser: brand→brand-soft gradient surface, warm-tinted month chip, In/Out/Net pills, a live 30-day net sparkline, and a glassy play affordance. Tap launches a full-screen Stories-style player with seven slide kinds (opening, cash in, cash out, top category, top payee, biggest expense, closing), animated count-up numbers, staggered slide-in entrances, layered halo illustrations, and tap-zones for prev / pause / next. Picks the most recent month with activity instead of going dark when the current calendar month is empty. A new Reports destination (launched from a labelled pill in the stats bar) bundles period chips (30D/90D/6M/12M), a four-cell KPI grid, weekly cashflow spline area, and a chip-row tab card containing the category donut + ranking, daily bar chart, calendar heatmap, and savings / expense ratios with target markers.
Login landing pairs a brand-soft tonal hero (logo + tagline + restored side illustration) with a clear eyebrow / display heading and tonal primary / outlined / OAuth buttons under an OR divider. Email login swaps the heavy default TabBar for a tonal segmented Email/Phone control, gains labelled fields, and adopts the shared page header so back navigation and styling match the rest of the app. Onboarding is a three-slide carousel with curated illustrations: - Simplified personal finance (wallet) - Automated (AI importer reads receipts, PDFs, CSV, XLS; integrations like Plaid and MCP) - Open source (yours to inspect, host and trust) Theme toggle lives in the top-right of both onboarding and the login landing so visitors can flip light / dark before they sign in. The white wordmark variant is in for dark mode.
Code Review SummaryThis PR delivers a significant UI refresh and introduces core functionality for AI-assisted chat and Budget management. It centralizes design tokens and standardizes the top bar across the app. 🚀 Key Improvements
💡 Minor Suggestions
|
| } | ||
| if (candidate == null) { |
There was a problem hiding this comment.
The fallback logic to find the latest transaction month can be expensive if the list is large. Since transactions are typically sorted or can be reduced more efficiently, consider optimizing the search.
| } | |
| if (candidate == null) { | |
| if (candidate == null && transactions.isNotEmpty) { | |
| DateTime latest = transactions.first.transaction.datetime; | |
| for (final t in transactions) { | |
| if (t.transaction.datetime.isAfter(latest)) { | |
| latest = t.transaction.datetime; | |
| } | |
| } | |
| candidate = DateTime(latest.year, latest.month, 1); | |
| } |
|
|
||
| void _openSearch() { | ||
| setState(() => _searching = true); | ||
| WidgetsBinding.instance.addPostFrameCallback((_) => _focus.requestFocus()); |
There was a problem hiding this comment.
It is safer to use SchedulerBinding or check if the focus node is still attached before requesting focus to avoid potential 'FocusNode used after dispose' errors during fast navigation transitions.
| WidgetsBinding.instance.addPostFrameCallback((_) => _focus.requestFocus()); | |
| WidgetsBinding.instance.addPostFrameCallback((_) { | |
| if (_focus.canRequestFocus) _focus.requestFocus(); | |
| }); |
Home keeps the dedicated drawer trigger SVG icon and shows the Trakli wordmark logo in the title (icon-only mark is reserved for non-home root pages). Profile uses a tonal brand hero with avatar derived from the user's initials (deterministic hashed pastel background), name and email beside it, plus a single grouped surface for Account info and Log out as tonal action tiles. Bottom navigation active state now uses the Trakli warm accent so the "you are here" signal pops without competing with the dominant green.
Add-transaction adopts the shared page header and replaces the saturated green TabBar with a tonal segmented Expense/Income picker tinted by context. Body switches via IndexedStack behind a horizontal-drag detector so swipe still flips the tab but content swaps instantly, removing the cross-fade flash on the Record button colour change. The Record button accent is now the canonical danger red on expense and primary green on income, instead of always green.
Record button gains a gradient surface (accent to slightly darker accent), a soft inner highlight, an accent-tinted shadow, and a tighter corner radius so the primary action feels lifted instead of flat. Currency chip and the "+" add affordances next to the wallet, party and category dropdowns share that family: calm tinted background at 10-12% accent alpha with a matching border and accent-coloured glyph, so they read as siblings of the form's segmented picker.
Filter triggers on the transaction history page (Date, Category, Wallet) are now per-filter tonal chips — Date in brand green, Category in warm orange, Wallet in income green. Each chip fills with its tone, hairline accent border, deep colour for icon, label and caret. The title also drops Title Case for sentence case to match the rest of the copy.
…ings Settings root, account info, account & privacy, advanced, defaults, display, notification settings, sync history, orphaned-media log, data deletion, transfers, savings (list + add), and notifications all drop the saturated green primary-colour CustomAppBar in favour of the shared page header so navigation chrome, typography and back behaviour match the rest of the app. The settings root wraps its tiles in a single tonal section card so the page reads as one grouped surface instead of free-floating list items.
Pins the recap fallback (must surface the most recent active month, not null when this calendar month is empty) and the savings-rate safety when income is zero. Locks the canonical Trakli green and orange to their hex values so palette refactors can't silently drift the brand. Covers PageAppBar's search-in-title transform and the brand-mark anchor on root pages. Existing party-card test updated to assert against the tonal palette instead of the now-removed hardcoded avatar hex values.
Users can ask Trakli questions from a dedicated bottom-nav tab, see assistant replies arrive via polling, and browse or resume past conversations through a history sheet. Empty state offers suggested prompts so a first question is one tap away. The new tab replaces the former profile slot; profile is now reached from the home app bar.
Parties, wallets, groups and categories now carry the same quiet three-motif background pattern as the AI tab, each scoped to its own representative icon. Gives every landing surface a subtle identity without affecting content or interactions.
Replace the manual refresh button with a gradient initials avatar that opens the profile screen. Pull-to-refresh on the body continues to sync, so the refresh capability is unchanged. Notifications icon remains.
Wallet-to-wallet transfers were buried behind a per-wallet overflow menu. Surface them alongside expense and income on the add-transaction screen so the action is discoverable from the primary entry point. The transfer form renders inline (no extra navigation) and saves through the existing flow. The original per-wallet entry remains.
Dark mode left input borders transparent by default, so text fields and selectors had no visible outline at rest while the focused state showed a brand-colored ring. Apply a subtle visible border colour for the unfocused state so every field reads as bounded in dark mode while staying quiet in light.
Profile was opened as a pushed route but explicitly hid its back button, leaving the only way back as the system gesture. Restore the default back affordance.
Budgets can now be created and managed on mobile, matching the web feature. Each budget has a period (weekly, monthly, yearly, or custom), an optional target scope across categories, wallets, or groups, threshold and forecast alerts, and rollover support. The detail screen fetches live progress for the current period and shows past closed-period snapshots once they sync. Open it from the new Budgets entry in the sidebar.
- Updated all language translation files - Fixed critical bug: 'Budgets' menu item now properly localized - Update the budget screens to use proper bloc and cubit patterns - Update budgets screens to use default custom snack bar
Extract use cases for AI Chat and Budget features to align with established architectural patterns used across other features. Update AiChatCubit and BudgetCubit to inject use cases instead of repositories, replacing all repository method calls with use case invocations. Regenerate DI configuration to auto-register new use cases. This eliminates architectural inconsistency and improves maintainability by ensuring all features follow the unified UseCase<T, Params> pattern with dependency injection.
- Compute budget progress locally (compute_local_progress) and persist it to the budgets.progress column via BudgetProgressRecomputer; recompute on transaction/target/budget mutations. - Add budget self-heal: recompute budgets when a fresh exchange rate is cached (ExchangeRateRepository.onExchangeRateUpdated), folding in foreign-currency transactions that were excluded while no rate was available. - Exclude un-convertible foreign-currency transactions from the bar instead of surfacing a misleading cross-currency total; drop currencyMismatchCount / currencyMismatchRawAmount from entity, DTO, mapper, UI, and translations. - Restrict the add-budget currency picker to wallet currencies + app default. - Persist progress through the datasource (updateBudgetProgressByServerId) rather than touching Drift directly in the repository. - Add read-only BudgetPeriodState sync handler + DTO.
d8e1c36 to
34dfb6e
Compare
| final BudgetTargetType type; | ||
| final String clientId; | ||
| const BudgetTargetInput({required this.type, required this.clientId}); | ||
| } |
There was a problem hiding this comment.
It is safer to use a dedicated slugification library or a more robust regex to handle special characters and non-ASCII names to prevent invalid slugs.
| } | |
| String _slugify(String name) { | |
| return name | |
| .trim() | |
| .toLowerCase() | |
| .replaceAll(RegExp(r'[^a-z0-9]+'), '-') | |
| .replaceAll(RegExp(r'^-+|-+$'), ''); | |
| } |
| String? transferClientId, | ||
| }) async { | ||
| return database.transaction(() async { | ||
| // Capture original tag-set before the mutation so we can also recompute |
There was a problem hiding this comment.
Fetching categories and transactions before the main transaction logic increases DB overhead. Consider moving this logic inside the database.transaction block or using a more optimized query to fetch only necessary IDs.
| // Capture original tag-set before the mutation so we can also recompute | |
| final result = await database.transaction(() async { | |
| final originalSnapshot = await (database.select(database.transactions) | |
| ..where((t) => t.clientId.equals(id))) | |
| .getSingleOrNull(); |
Description
Cosmetic UI changes to improve UX and UI.
Type of Change
Cosmetic UI changes.