Skip to content

Conversation

@zensgit
Copy link
Owner

@zensgit zensgit commented Sep 29, 2025

Adds banks table migration, list endpoint (GET /api/v1/banks), Flutter BankSelector with search and optional embedding in AccountAddScreen. No category/template changes included.\n\n- API: jive-api/src/handlers/banks.rs, route wired in main.rs\n- Flutter: models/bank.dart, services/bank_service.dart, ui/components/banks/bank_selector.dart\n- Screen: AccountAddScreen optional BankSelector field (no payload change yet)\n\nFollow-ups: bank version endpoint, static bank_icons route, wire bank_id on save.

… component and embed in AccountAddScreen (optional)
@Copilot Copilot AI review requested due to automatic review settings September 29, 2025 00:34
@gemini-code-assist
Copy link

Summary of Changes

Hello @zensgit, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the foundational infrastructure for managing and selecting banks and financial institutions within the application. It encompasses database schema changes, a new API endpoint for data retrieval, and a comprehensive Flutter component for user interaction. This work establishes a robust framework for future enhancements related to bank integration and account management.

Highlights

  • Database Migration: A new banks table has been added to the database, designed to store detailed information about banks and financial institutions. This table includes fields for various names (Chinese, English), Pinyin for search, icon filenames, and a flag for cryptocurrencies, along with indexing for efficient querying.
  • Bank Listing API Endpoint: A new GET endpoint, /api/v1/banks, has been implemented in the Rust backend. This endpoint allows clients to retrieve a list of banks, supporting query parameters for searching by name (Chinese, English, Pinyin), filtering by is_crypto status, and limiting the number of results.
  • Flutter Bank Model and Service: New Bank model and BankService have been introduced in the Flutter application. The Bank model mirrors the backend structure, while the BankService handles fetching bank data from the API, including local caching with expiration to optimize performance and reduce network requests.
  • Flutter Bank Selector UI Component: A reusable BankSelector Flutter widget has been created. This component provides a user-friendly interface for selecting a bank or cryptocurrency through a searchable modal bottom sheet, offering a clear and interactive selection experience.
  • Static Bank Icon Serving: The Rust API now includes a new static route, /static/bank_icons, to serve bank icon files. This ensures that bank icons can be easily accessed and displayed within the Flutter application.
  • AccountAddScreen Integration: The AccountAddScreen in the Flutter app has been updated to optionally embed the new BankSelector component, laying the groundwork for associating accounts with specific banks or cryptocurrencies. Note that the actual bank_id payload change on save is a follow-up task.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a minimal bank selector feature with API backend support and Flutter UI components. It enables users to search and select banks or cryptocurrencies when adding accounts.

  • Added database structure for banks with search capabilities (pinyin support)
  • Implemented REST API endpoint for fetching banks with filtering options
  • Created Flutter components for bank selection with caching and search functionality

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
jive-api/migrations/031_create_banks_table.sql Database table creation for banks with indexes for search optimization
jive-api/src/models/bank.rs Bank model struct with display name helper method
jive-api/src/handlers/banks.rs API handler for listing banks with search and crypto filtering
jive-flutter/lib/models/bank.dart Flutter Bank model with JSON serialization and display properties
jive-flutter/lib/services/bank_service.dart Service layer with caching, API calls, and local filtering
jive-flutter/lib/ui/components/banks/bank_selector.dart UI component for bank selection with search modal
jive-flutter/lib/screens/accounts/account_add_screen.dart Minor updates to variable naming and comments

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +85 to +95
child: Text(
widget.selectedBank!.displayName[0].toUpperCase(),
style: theme.textTheme.bodySmall,
),
)
else
Icon(
widget.isCryptoMode
? Icons.currency_bitcoin
: Icons.account_balance,
size: 20,
Copy link

Copilot AI Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition checks if iconFilename is not null but shows a text avatar instead of the icon. The logic should be reversed - show the text avatar when iconFilename is null.

Suggested change
child: Text(
widget.selectedBank!.displayName[0].toUpperCase(),
style: theme.textTheme.bodySmall,
),
)
else
Icon(
widget.isCryptoMode
? Icons.currency_bitcoin
: Icons.account_balance,
size: 20,
backgroundImage: AssetImage(widget.selectedBank!.iconFilename!),
)
else
CircleAvatar(
radius: 12,
backgroundColor: theme.colorScheme.surfaceContainerHighest,
child: Text(
widget.selectedBank!.displayName[0].toUpperCase(),
style: theme.textTheme.bodySmall,
),

Copilot uses AI. Check for mistakes.

Comment on lines +347 to +356
child: Text(
bank.displayName[0].toUpperCase(),
style: theme.textTheme.titleMedium,
),
)
: Icon(
widget.isCryptoMode
? Icons.currency_bitcoin
: Icons.account_balance,
),
Copy link

Copilot AI Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same logic error as above - when iconFilename is not null, it shows a text avatar instead of using the actual icon. The condition should be reversed.

Suggested change
child: Text(
bank.displayName[0].toUpperCase(),
style: theme.textTheme.titleMedium,
),
)
: Icon(
widget.isCryptoMode
? Icons.currency_bitcoin
: Icons.account_balance,
),
backgroundImage: AssetImage(bank.iconFilename!),
)
: (widget.isCryptoMode
? Icon(Icons.currency_bitcoin)
: CircleAvatar(
backgroundColor: theme.colorScheme.surfaceContainerHighest,
child: Text(
bank.displayName[0].toUpperCase(),
style: theme.textTheme.titleMedium,
),
)),

Copilot uses AI. Check for mistakes.

Comment on lines +88 to +92
try {
return banks.firstWhere((bank) => bank.id == id);
} catch (e) {
return null;
}
Copy link

Copilot AI Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using firstWhere without orElse will throw a StateError if no bank is found. The catch block won't handle this exception type properly. Use firstWhere with orElse parameter or use where().firstOrNull if available.

Suggested change
try {
return banks.firstWhere((bank) => bank.id == id);
} catch (e) {
return null;
}
return banks.firstWhere(
(bank) => bank.id == id,
orElse: () => null,
);

Copilot uses AI. Check for mistakes.

borderRadius:
BorderRadius.circular(AppConstants.borderRadius),
),
suffixIcon: _searchController.text.isNotEmpty
Copy link

Copilot AI Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition won't update when the text changes because it's not wrapped in a StatefulBuilder or listening to controller changes. The clear button won't appear/disappear as expected during typing.

Copilot uses AI. Check for mistakes.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new bank selector feature, complete with a database migration, a new API endpoint, and a Flutter component. The implementation is a solid first step. My review focuses on several areas for improvement: ensuring consistency between client and server search logic, optimizing data fetching and caching strategies, improving code quality and maintainability in the Rust backend, and fixing a UI bug where bank icons are not displayed. Addressing these points will enhance the feature's correctness, performance, and user experience.

Comment on lines +51 to +70
let banks = query
.build()
.fetch_all(&pool)
.await
.map_err(|e| ApiError::DatabaseError(e.to_string()))?;

let mut response = Vec::new();
for row in banks {
response.push(Bank {
id: row.get("id"),
code: row.get("code"),
name: row.get("name"),
name_cn: row.get("name_cn"),
name_en: row.get("name_en"),
icon_filename: row.get("icon_filename"),
is_crypto: row.get("is_crypto"),
});
}

Ok(Json(response))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation manually maps each PgRow to the Bank struct. This is verbose and error-prone, as any change in the SELECT statement or Bank struct requires manual updates in two places.

You can use sqlx's automatic mapping capabilities to make the code more concise, readable, and safer. To do this, you'll need to:

  1. Add #[derive(sqlx::FromRow)] to your Bank struct definition in jive-api/src/models/bank.rs.
  2. Use build_query_as() and let sqlx handle the mapping.

This change significantly improves maintainability and reduces the chance of runtime errors from mismatched columns.

    let banks: Vec<Bank> = query
        .build_query_as()
        .fetch_all(&pool)
        .await
        .map_err(|e| ApiError::DatabaseError(e.to_string()))?;

    Ok(Json(banks))

Comment on lines +33 to +38
if (!forceRefresh) {
final cached = await _getCachedBanks();
if (cached != null && cached.isNotEmpty) {
return _filterBanksLocally(cached, search: search, isCrypto: isCrypto);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The caching logic in getBanks is flawed. When a search term is provided, it filters the locally cached list instead of querying the API. This leads to incorrect and incomplete search results because the client-side filtering is less capable than the backend's (e.g., no pinyin search).

The API should always be queried when a search parameter is present to ensure accurate results. The cache should only be used for requests that fetch the entire, unfiltered list.

    // Only use cache if not searching and not forcing a refresh.
    if (!forceRefresh && (search == null || search.isEmpty)) {
      final cached = await _getCachedBanks();
      if (cached != null && cached.isNotEmpty) {
        // Apply isCrypto filter locally if needed on the cached full list.
        return _filterBanksLocally(cached, isCrypto: isCrypto);
      }
    }

Comment on lines +187 to +201
void _filterBanks() {
final query = _searchController.text.toLowerCase();
setState(() {
if (query.isEmpty) {
_filteredBanks = widget.banks;
} else {
_filteredBanks = widget.banks.where((bank) {
return bank.name.toLowerCase().contains(query) ||
(bank.nameCn?.toLowerCase().contains(query) ?? false) ||
(bank.nameEn?.toLowerCase().contains(query) ?? false) ||
bank.code.toLowerCase().contains(query);
}).toList();
}
});
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The search functionality in the _BankPickerSheet is implemented purely on the client-side, filtering a pre-loaded list of banks. This is inconsistent with the backend's more powerful search capabilities (which include pinyin and abbreviations) and can lead to a poor user experience.

A better approach is to perform the search on the server. This typically involves:

  1. Debouncing the user's input in the search TextField to avoid excessive API calls.
  2. Calling the BankService to fetch results from the API with the search query (e.g., _bankService.getBanks(search: query)).
  3. Updating the UI with the results from the API, including loading and error states for the search operation itself.

This will provide a more consistent, accurate, and scalable search experience.

name_cn_pinyin VARCHAR(200),
name_cn_abbr VARCHAR(50),
icon_filename VARCHAR(100),
icon_url TEXT,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The icon_url column is defined in the banks table schema but appears to be unused. The API handler in jive-api/src/handlers/banks.rs does not select this column, and the Flutter client in jive-flutter/lib/models/bank.dart dynamically constructs the icon URL from icon_filename. To avoid confusion and redundant data storage, this column should be removed.

updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_banks_code ON banks(code);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The index idx_banks_code on the code column is redundant. PostgreSQL automatically creates an index for columns with a UNIQUE constraint, which the code column has. Removing this explicit CREATE INDEX statement will simplify the migration script without affecting performance.

Comment on lines +27 to +40
if let Some(search) = params.search {
query.push(" AND (");
query.push("name_cn ILIKE ");
query.push_bind(format!("%{}%", search));
query.push(" OR name ILIKE ");
query.push_bind(format!("%{}%", search));
query.push(" OR name_en ILIKE ");
query.push_bind(format!("%{}%", search));
query.push(" OR name_cn_pinyin ILIKE ");
query.push_bind(format!("%{}%", search));
query.push(" OR name_cn_abbr ILIKE ");
query.push_bind(format!("%{}%", search));
query.push(")");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for building the search query involves repeating query.push(...) and query.push_bind(...) for each searchable field. This can be made more maintainable and less repetitive by iterating over a list of column names.

    if let Some(search) = params.search {
        let search_pattern = format!("%{}%", search);
        let search_fields = ["name_cn", "name", "name_en", "name_cn_pinyin", "name_cn_abbr"];

        query.push(" AND (");

        for (i, field) in search_fields.iter().enumerate() {
            if i > 0 {
                query.push(" OR ");
            }
            query.push(format!("{} ILIKE ", field));
            query.push_bind(search_pattern.clone());
        }

        query.push(")");
    }

Comment on lines +22 to +24
String? get iconUrl => iconFilename != null
? '/static/bank_icons/$iconFilename'
: null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The iconUrl getter constructs a relative URL. When using this with Image.network, you'll need to prepend the API's base URL. To make this model more self-contained and easier to use across the app, consider making the base URL available here, perhaps through a static configuration class, and returning a full URL.

Comment on lines +86 to +93
Future<Bank?> getBankById(String id) async {
final banks = await getBanks();
try {
return banks.firstWhere((bank) => bank.id == id);
} catch (e) {
return null;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The getBankById method currently fetches the entire list of banks and then finds the one with the matching ID on the client side. This is inefficient, especially as the number of banks grows, as it leads to unnecessary data transfer and processing.

For better performance, consider adding a dedicated API endpoint to fetch a single bank by its ID, such as GET /api/v1/banks/:id. This would make the lookup much more efficient.

Comment on lines +81 to +96
if (widget.selectedBank!.iconFilename != null)
CircleAvatar(
radius: 12,
backgroundColor: theme.colorScheme.surfaceContainerHighest,
child: Text(
widget.selectedBank!.displayName[0].toUpperCase(),
style: theme.textTheme.bodySmall,
),
)
else
Icon(
widget.isCryptoMode
? Icons.currency_bitcoin
: Icons.account_balance,
size: 20,
),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The bank icon is not being displayed. The code checks for iconFilename but then displays a CircleAvatar with the first letter of the bank's name instead of the actual icon image. This happens for both the selected bank display and the items in the picker list.

To fix this, you should use an Image.network widget to display the icon. You'll need to construct the full URL using your API's base URL (e.g., from ApiConfig).

Example for the selected bank:

if (widget.selectedBank!.iconUrl != null) {
  Image.network(
    ApiConfig.apiUrl + widget.selectedBank!.iconUrl!,
    width: 24,
    height: 24,
    errorBuilder: (context, error, stackTrace) => Icon(
      widget.isCryptoMode ? Icons.currency_bitcoin : Icons.account_balance,
      size: 20,
    ),
  );
} else {
  // ... fallback icon
}

A similar change is needed in _BankPickerSheet for the ListTile's leading widget.

zensgit and others added 5 commits September 29, 2025 08:38
- Remove REFERENCES payees(id) constraint (payees table doesn't exist yet)
- Add TODO comment for future FK constraint
- Comment out account module (not in this branch)
- Fixes CI compilation error: relation payees does not exist

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
… transaction_provider

- Resolved conflict in family_settings_service.dart by keeping main's parameterized method calls
- Resolved conflict in transaction_provider.dart by removing duplicate enum and method definitions
- Both files now compile without errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
zensgit added a commit that referenced this pull request Oct 8, 2025
Resolved git merge conflicts that were polluting all feature branches:
- theme_management_screen.dart: 5 conflicts (ScaffoldMessenger patterns)
- transaction_provider.dart: 2 conflicts (duplicate enum & method definitions)
- family_activity_log_screen.dart: 1 conflict (statistics loading)

All conflicts resolved by:
- Preferring messenger variable pattern over repeated ScaffoldMessenger.of()
- Removing duplicate enum and method definitions
- Using direct stats return instead of _parseActivityStatistics()

Added comprehensive PR fix report documenting all 5 PRs analyzed.

This fix will automatically benefit all PRs (#65, #66, #68, #69, #70) as they
rebase/merge from main.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Resolved conflicts in transaction_provider.dart by accepting main's clean version.
Main branch already contains fixes for duplicate enums and methods.
@zensgit zensgit merged commit 1bfb42c into main Oct 8, 2025
8 checks passed
@zensgit zensgit deleted the feature/bank-selector-min branch October 8, 2025 06:30
zensgit added a commit that referenced this pull request Oct 8, 2025
Merged after resolving conflicts with updated main (which includes PR #68)
@zensgit zensgit mentioned this pull request Oct 8, 2025
3 tasks
zensgit added a commit that referenced this pull request Oct 11, 2025
Resolve merge conflicts:
- jive-api/src/main.rs: Keep bank icons static route at end
- jive-flutter/lib/services/family_settings_service.dart: Remove extra blank line

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@zensgit zensgit mentioned this pull request Oct 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant