Commit Graph

24 Commits

Author SHA1 Message Date
Juan José Mata
c09362b880 Check for pending invitations before creating new Family during SSO log in/sign up (#1171)
* Check for pending invitations before creating new Family during SSO account creation

When a user signs in via Google SSO and doesn't have an account yet, the
system now checks for pending invitations before creating a new Family.
If an invitation exists, the user joins the invited family instead.

- OidcAccountsController: check Invitation.pending in link/create_user
- API AuthController: check pending invitations in sso_create_account
- SessionsController: pass has_pending_invitation to mobile SSO callback
- Web view: show "Accept Invitation" button when invitation exists
- Flutter: show "Accept Invitation" tab/button when invitation pending

https://claude.ai/code/session_019Tr6edJa496V1ErGmsbqFU

* Fix external assistant tests: clear Settings cache to prevent test pollution

The tests relied solely on with_env_overrides to clear configuration, but
rails-settings-cached may retain stale Setting values across tests when
the cache isn't explicitly invalidated. Ensure both ENV vars AND Setting
values are cleared with Setting.clear_cache before assertions.

https://claude.ai/code/session_019Tr6edJa496V1ErGmsbqFU

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-10 13:38:42 +01:00
Juan José Mata
f6e7234ead Enable Google SSO account creation in Flutter app (#1164)
* Add Google SSO onboarding flow for Flutter mobile app

Previously, mobile users attempting Google SSO without a linked OIDC
identity received an error telling them to link from the web app first.
This adds the same account linking/creation flow that exists on the PWA.

Backend changes:
- sessions_controller: Cache pending OIDC auth with a linking code and
  redirect back to the app instead of returning an error
- api/v1/auth_controller: Add sso_link endpoint to link Google identity
  to an existing account via email/password, and sso_create_account
  endpoint to create a new SSO-only account (respects JIT config)
- routes: Add POST auth/sso_link and auth/sso_create_account

Flutter changes:
- auth_service: Detect account_not_linked callback status, add ssoLink
  and ssoCreateAccount API methods
- auth_provider: Track SSO onboarding state, expose linking/creation
  methods and cancelSsoOnboarding
- sso_onboarding_screen: New screen with tabs to link existing account
  or create new account, pre-filled with Google profile data
- main.dart: Show SsoOnboardingScreen when ssoOnboardingPending is true

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Fix broken SSO tests: use MemoryStore cache and correct redirect param

- Sessions test: check `status` param instead of `error` since
  handle_mobile_sso_onboarding sends linking info with status key
- API auth tests: swap null_store for MemoryStore so cache-based
  linking code validation works in test environment

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Delay linking-code consumption until SSO link/create succeeds

Split validate_and_consume_linking_code into validate_linking_code
(read-only) and consume_linking_code! (delete). The code is now only
consumed after password verification (sso_link) or successful user
save (sso_create_account), so recoverable errors no longer burn the
one-time code and force a full Google SSO roundtrip.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Make linking-code consumption atomic to prevent race conditions

Move consume_linking_code! (backed by Rails.cache.delete) to after
recoverable checks (bad password, policy rejection) but before
side-effecting operations (identity/user creation). Only the first
caller to delete the cache key gets true, so concurrent requests
with the same code cannot both succeed.

- sso_link: consume after password auth, before OidcIdentity creation
- sso_create_account: consume after allow_account_creation check,
  before User creation
- Bad password still preserves the code for retry
- Add single-use regression tests for both endpoints

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Add missing sso_create_account test coverage for blank code and validation failure

- Test blank linking_code returns 400 (bad_request) with proper error
- Test duplicate email triggers user.save failure → 422 with validation errors

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Verify cache payload in mobile SSO onboarding test with MemoryStore

The test environment uses :null_store which silently discards cache
writes, so handle_mobile_sso_onboarding's Rails.cache.write was never
verified. Swap in a MemoryStore for this test and assert the full
cached payload (provider, uid, email, name, device_info,
allow_account_creation) at the linking_code key from the redirect URL.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Add rswag/OpenAPI specs for sso_link and sso_create_account endpoints

POST /api/v1/auth/sso_link: documents linking_code + email/password
params, 200 (tokens), 400 (missing code), 401 (invalid creds/expired).

POST /api/v1/auth/sso_create_account: documents linking_code +
optional first_name/last_name params, 200 (tokens), 400 (missing code),
401 (expired code), 403 (creation disabled), 422 (validation errors).

Note: RAILS_ENV=test bundle exec rake rswag:specs:swaggerize should be
run to regenerate docs/api/openapi.yaml once the runtime environment
matches the Gemfile Ruby version.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Preserve OIDC issuer through mobile SSO onboarding flow

handle_mobile_sso_onboarding now caches the issuer from
auth.extra.raw_info.iss so it survives the linking-code round trip.
build_omniauth_hash populates extra.raw_info.iss from the cached
issuer so OidcIdentity.create_from_omniauth stores it correctly.

Previously the issuer was always nil for mobile SSO-created identities
because build_omniauth_hash passed an empty raw_info OpenStruct.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Block MFA users from bypassing second factor via sso_link

sso_link authenticated with email/password but never checked
user.otp_required?, allowing MFA users to obtain tokens without
a second factor. The mobile SSO callback already rejects MFA users
with "mfa_not_supported"; apply the same guard in sso_link before
consuming the linking code or creating an identity.

Returns 401 with mfa_required: true, consistent with the login
action's MFA response shape.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Fix NoMethodError in SSO link MFA test

Replace non-existent User.generate_otp_secret class method with
ROTP::Base32.random(32), matching the pattern used in User#setup_mfa!.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

* Assert linking code survives rejected SSO create account

Add cache persistence assertion to "should reject SSO create account
when not allowed" test, verifying the linking code is not consumed on
the 403 path. This mirrors the pattern used in the invalid-password
sso_link test.

The other rejection tests (expired/missing linking code) don't have a
valid cached code to check, so no assertion is needed there.

https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-09 16:47:32 +01:00
Juan José Mata
1f9a934c59 Add build ID to Flutter app version display 2026-02-23 14:22:54 +00:00
Juan José Mata
ad3087f1dd Improvements to Flutter client (#1042)
* Chat improvements

* Delete/reset account via API for Flutter app

* Fix tests.

* Add "contact us" to settings

* Update mobile/lib/screens/chat_conversation_screen.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Improve LLM special token detection

* Deactivated user shouldn't have API working

* Fix tests

* API-Key usage

* Flutter app launch failure on no network

* Handle deletion/reset delays

* Local cached data may become stale

* Use X-Api-Key correctly!

---------

Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-22 21:22:32 -05:00
Juan José Mata
a16e1f8482 Default tap on URL opens settings 2026-02-18 15:51:17 +01:00
Juan José Mata
2c50bd1d9a Add demo account to Flutter client also 2026-02-18 11:22:44 +01:00
Juan José Mata
42724335ab Default login for Flutter client beta 2026-02-18 11:17:44 +01:00
Juan José Mata
da754b8d05 Version number to bundle files 2026-02-18 00:23:02 +01:00
Juan José Mata
1995c62ddf Flutter title and icon alignment fixes 2026-02-16 04:37:18 +01:00
Juan José Mata
2dcb4b4f67 Small Flutter UI tweaks 2026-02-16 04:23:00 +01:00
Juan José Mata
eb0d05a7fb Intro mode in Flutter client fixes 2026-02-16 02:17:03 +01:00
Juan José Mata
bf0be85859 Expose ui_layout and ai_enabled to mobile clients and add enable_ai endpoint (#983)
* Wire ui layout and AI flags into mobile auth

Include ui_layout and ai_enabled in mobile login/signup/SSO payloads,
add an authenticated endpoint to enable AI from Flutter, and gate
mobile navigation based on intro layout and AI consent flow.

* Linter

* Ensure write scope on enable_ai

* Make sure AI is available before enabling it

* Test improvements

* PR comment

* Fix review issues: test assertion bug, missing coverage, and Dart defaults (#985)

- Fix login test to use ai_enabled? (method) instead of ai_enabled (column)
  to match what mobile_user_payload actually serializes
- Add test for enable_ai when ai_available? returns false (403 path)
- Default aiEnabled to false when user is null in AuthProvider to avoid
  showing AI as available before authentication completes
- Remove extra blank lines in auth_provider.dart and auth_service.dart

https://claude.ai/code/session_01LEYYmtsDBoqizyihFtkye4

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-14 00:39:03 +01:00
Juan José Mata
32b01165c9 Flutter login polish (#973)
* Skip config screen by setting demo site default

* Small copy edits

* Login page polish
2026-02-12 23:36:21 +01:00
Dream
ca3abd5d8b Add Google Sign-In (SSO) support to Flutter mobile app (#860)
* Add mobile SSO support to sessions controller

Add /auth/mobile/:provider route and mobile_sso_start action that
captures device params in session and renders an auto-submitting POST
form to OmniAuth (required by omniauth-rails_csrf_protection).

Modify openid_connect callback to detect mobile_sso session, issue
Doorkeeper tokens via MobileDevice, and redirect to sureapp://oauth/callback
with tokens. Handles MFA users and unlinked accounts with error redirects.

Validates provider name against configured SSO providers and device info
before proceeding.

* Add SSO auth flow to Flutter service and provider

Add buildSsoUrl() and handleSsoCallback() to AuthService for
constructing the mobile SSO URL and parsing tokens from the deep
link callback.

Add startSsoLogin() and handleSsoCallback() to AuthProvider for
launching browser-based SSO and processing the redirect.

* Register deep link listener for SSO callback

Listen for sureapp://oauth/* deep links via app_links package,
handling both cold start (getInitialLink) and warm (uriLinkStream)
scenarios. Routes callbacks to AuthProvider.handleSsoCallback().

* Add Google Sign-In button to Flutter login screen

Add "or" divider and outlined Google Sign-In button that triggers
browser-based SSO via startSsoLogin('google_oauth2').

Add app_links and url_launcher dependencies to pubspec.yaml.

* Fix mobile SSO failure handling to redirect back to app

When OmniAuth fails during mobile SSO flow, redirect to
sureapp://oauth/callback with the error instead of the web login page.
Cleans up mobile_sso session data on failure.

* Address PR review feedback for mobile SSO flow

- Use strong params for device info in mobile_sso_start
- Guard against nil session data in handle_mobile_sso_callback
- Add error handling for AppLinks initialization and stream
- Handle launchUrl false return value in SSO login
- Use user-friendly error messages instead of exposing exceptions
- Reject empty token strings in SSO callback validation

* Consolidate mobile device token logic into MobileDevice model

Extract duplicated device upsert and token issuance code from
AuthController and SessionsController into MobileDevice. Add
CALLBACK_URL constant and URL builder helpers to eliminate repeated
deep-link strings. Add mobile SSO integration tests covering the
full flow, MFA rejection, unlinked accounts, and failure handling.

* Fix CI: resolve Brakeman redirect warnings and rubocop empty line

Move mobile SSO redirect into a private controller method with an
inline string literal so Brakeman can statically verify the target.
Remove unused URL builder helpers from MobileDevice. Fix extra empty
line at end of AuthController class body.

* Use authorization code exchange for mobile SSO and add signup error handling

Replace passing plaintext tokens in mobile SSO redirect URLs with a
one-time authorization code pattern. Tokens are now stored server-side
in Rails.cache (5min TTL) and exchanged via a secure POST to
/api/v1/auth/sso_exchange. Also wraps device/token creation in the
signup action with error handling and sanitizes device error messages.

* Add error handling for login device registration and blank SSO code guard

* Address PR #860 review: fix SSO race condition, add OpenAPI spec, and cleanup

- Fix race condition in sso_exchange by checking Rails.cache.delete return
  value to ensure only one request can consume an authorization code
- Use strong parameters (params.require) for sso_exchange code param
- Move inline HTML from mobile_sso_start to a proper view template
- Clear stale session[:mobile_sso] flag on web login paths to prevent
  abandoned mobile flows from hijacking subsequent web SSO logins
- Add OpenAPI/rswag spec for all auth API endpoints

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix mobile SSO test to match authorization code exchange pattern

The test was asserting tokens directly in the callback URL, but the code
uses an authorization code exchange pattern. Updated to exchange the code
via the sso_exchange API endpoint. Also swaps in a MemoryStore for this
test since the test environment uses null_store which discards writes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Refactor mobile OAuth to use single shared application

Replace per-device Doorkeeper::Application creation with a shared
"Sure Mobile" OAuth app. Device tracking uses mobile_device_id on
access tokens instead of oauth_application_id on mobile_devices.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 00:45:11 +01:00
Juan José Mata
4be1c39e1f Fix Flutter iOS build and add web support with web-safe storage (#878)
* Add Flutter web support and web-safe storage

* Update mobile/web/index.html

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Product name instead of placeholder

---------

Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-03 12:29:38 +01:00
Lazy Bone
2ac3f3dff0 Add account filtering and net worth card to dashboard (#818)
* feat(mobile): optimize asset/liability display with filters

- Add NetWorthCard widget with placeholder for future net worth API
- Add side-by-side Assets/Liabilities display with tap-to-filter
- Implement CurrencyFilter widget for multi-select currency filtering
- Replace old _SummaryCard with new unified design
- Remove _CollapsibleSectionHeader in favor of filter-based navigation

The net worth section shows a placeholder as the API endpoint is not yet available.
Users can now filter accounts by type (assets/liabilities) and by currency.

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* fix(mobile): remove unused variables and add const

- Remove unused _totalAssets, _totalLiabilities, _getPrimaryCurrency
- Add const to Text('All') widget

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* feat(mobile): enhance dashboard with icons, long-press breakdown, and grouped view

- NetWorthCard: replace text labels with trending icons, add colored
  bottom borders for asset (green) and liability (red) sections
- Add long-press gesture on asset/liability areas to show full currency
  breakdown in a bottom sheet popup
- Add collapsible account type grouping (Crypto, Bank, Investment, etc.)
  with type-specific icons and expand/collapse headers
- Add PreferencesService for persisting display settings
- Add "Group by Account Type" toggle in Settings screen
- Wire settings change to dashboard via GlobalKey for live updates

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* refactor(mobile): remove welcome header from dashboard

Strip the Welcome greeting and subtitle to let the financial
overview take immediate focus.

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* feat(mobile): compact filter buttons with scroll-wheel currency switcher

- Remove trending icons from asset/liability filter buttons
- Increase amount font size to titleMedium bold
- Reduce Net Worth section and filter button padding
- Show single currency at a time with ListWheelScrollView for
  scrolling between currencies (wheel-picker style)
- Absorb scroll events via NotificationListener to prevent
  triggering pull-to-refresh
- Keep icons in the long-press currency breakdown popup

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* fix(mobile): prevent bottom sheet overflow with ConstrainedBox

Use ConstrainedBox + ListView.separated with shrinkWrap for the
currency breakdown popup. Few currencies: sheet sizes to content.
Many currencies: caps at 50% screen height and scrolls.

Also add isScrollControlled and useSafeArea to showModalBottomSheet.

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* fix(mobile): reload dashboard preferences on any tab switch to Home

Previously only reloaded when navigating directly from Settings to
Home. Now reloads whenever the Home tab is selected, covering paths
like Settings -> More -> Home.

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* chore(mobile): simplify net worth placeholder to single line

Replace the two-line Net Worth / -- placeholder with a compact
"Net Worth — coming soon" label while the API endpoint is pending.

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-02 09:47:01 +01:00
Lazy Bone
81cf473862 fix: Use getValidAccessToken() in connectivity banner sync button (#851)
* feat(mobile): Add transaction display on calendar date tap

Implement two-tap interaction for calendar dates:
- First tap selects a date (highlighted with thicker primary color border)
- Second tap on same date shows AlertDialog with transactions for that day

Each transaction displays with:
- Color-coded icon (red minus for expenses, green plus for income)
- Transaction name as title
- Notes as subtitle (if present)
- Amount with color matching expense/income

Selection is cleared when changing account, account type, or month.

https://claude.ai/code/session_019m7ZrCakU6h9xLwD1NTx9i

* feat(mobile): optimize asset/liability display with filters

- Add NetWorthCard widget with placeholder for future net worth API
- Add side-by-side Assets/Liabilities display with tap-to-filter
- Implement CurrencyFilter widget for multi-select currency filtering
- Replace old _SummaryCard with new unified design
- Remove _CollapsibleSectionHeader in favor of filter-based navigation

The net worth section shows a placeholder as the API endpoint is not yet available.
Users can now filter accounts by type (assets/liabilities) and by currency.

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* fix(mobile): remove unused variables and add const

- Remove unused _totalAssets, _totalLiabilities, _getPrimaryCurrency
- Add const to Text('All') widget

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* feat(mobile): enhance dashboard with icons, long-press breakdown, and grouped view

- NetWorthCard: replace text labels with trending icons, add colored
  bottom borders for asset (green) and liability (red) sections
- Add long-press gesture on asset/liability areas to show full currency
  breakdown in a bottom sheet popup
- Add collapsible account type grouping (Crypto, Bank, Investment, etc.)
  with type-specific icons and expand/collapse headers
- Add PreferencesService for persisting display settings
- Add "Group by Account Type" toggle in Settings screen
- Wire settings change to dashboard via GlobalKey for live updates

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* refactor(mobile): remove welcome header from dashboard

Strip the Welcome greeting and subtitle to let the financial
overview take immediate focus.

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* feat(mobile): compact filter buttons with scroll-wheel currency switcher

- Remove trending icons from asset/liability filter buttons
- Increase amount font size to titleMedium bold
- Reduce Net Worth section and filter button padding
- Show single currency at a time with ListWheelScrollView for
  scrolling between currencies (wheel-picker style)
- Absorb scroll events via NotificationListener to prevent
  triggering pull-to-refresh
- Keep icons in the long-press currency breakdown popup

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* feat: Add API key login option to mobile app

Add a "Via API Key Login" button on the login screen that opens a
dialog for entering an API key. The API key is validated by making a
test request to /api/v1/accounts with the X-Api-Key header, and on
success is persisted in secure storage. All HTTP services now use a
centralized ApiConfig.getAuthHeaders() helper that returns the correct
auth header (X-Api-Key or Bearer) based on the current auth mode.

https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH

* fix: Improve API key dialog context handling and controller disposal

- Use outer context for SnackBar so it displays on the main screen
  instead of behind the dialog
- Explicitly dispose TextEditingController to prevent memory leaks
- Close dialog on failure before showing error SnackBar for better UX
- Avoid StatefulBuilder context parameter shadowing

https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH

* fix: Use user-friendly error message in API key login catch block

Log the technical exception details via LogService.instance.error and
show a generic "Unable to connect" message to the user instead of
exposing the raw exception string.

https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH

* fix: Use getValidAccessToken() in connectivity banner sync button

Replace direct authProvider.tokens?.accessToken access with
getValidAccessToken() so the Sync Now button works in API-key
auth mode where _tokens is null.

https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH

* Revert "fix: Use getValidAccessToken() in connectivity banner sync button"

This reverts commit 7015c160f0.

* Reapply "fix: Use getValidAccessToken() in connectivity banner sync button"

This reverts commit b29e010de3.

* fix: Use getValidAccessToken() in connectivity banner sync button

Replace direct authProvider.tokens?.accessToken access with
getValidAccessToken() so the Sync Now button works in API-key
auth mode where _tokens is null.

https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH

* fix(mobile): prevent bottom sheet overflow with ConstrainedBox

Use ConstrainedBox + ListView.separated with shrinkWrap for the
currency breakdown popup. Few currencies: sheet sizes to content.
Many currencies: caps at 50% screen height and scrolls.

Also add isScrollControlled and useSafeArea to showModalBottomSheet.

https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3

* fix: Prevent multiple syncs and handle auth errors in connectivity banner

Set _isSyncing immediately on tap to disable the button during token
refresh, wrap getValidAccessToken() in try/catch with user-facing error
snackbar, and await _handleSync so errors propagate correctly.

https://claude.ai/code/session_01GgVgjqwyXhWMZN3eWfaMCk

---------

Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 23:16:02 +01:00
Lazy Bone
77269fa60a feat(mobile): Add transaction display on calendar date tap (#817)
Implement two-tap interaction for calendar dates:
- First tap selects a date (highlighted with thicker primary color border)
- Second tap on same date shows AlertDialog with transactions for that day

Each transaction displays with:
- Color-coded icon (red minus for expenses, green plus for income)
- Transaction name as title
- Notes as subtitle (if present)
- Amount with color matching expense/income

Selection is cleared when changing account, account type, or month.

https://claude.ai/code/session_019m7ZrCakU6h9xLwD1NTx9i

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 23:13:10 +01:00
Lazy Bone
38938fe971 Add API key authentication support to mobile app (#850)
* feat: Add API key login option to mobile app

Add a "Via API Key Login" button on the login screen that opens a
dialog for entering an API key. The API key is validated by making a
test request to /api/v1/accounts with the X-Api-Key header, and on
success is persisted in secure storage. All HTTP services now use a
centralized ApiConfig.getAuthHeaders() helper that returns the correct
auth header (X-Api-Key or Bearer) based on the current auth mode.

https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH

* fix: Improve API key dialog context handling and controller disposal

- Use outer context for SnackBar so it displays on the main screen
  instead of behind the dialog
- Explicitly dispose TextEditingController to prevent memory leaks
- Close dialog on failure before showing error SnackBar for better UX
- Avoid StatefulBuilder context parameter shadowing

https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH

* fix: Use user-friendly error message in API key login catch block

Log the technical exception details via LogService.instance.error and
show a generic "Unable to connect" message to the user instead of
exposing the raw exception string.

https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-31 13:25:52 +01:00
Lazy Bone
62dabb6971 Fix: Transaction Sync Issues & Enhanced Debugging (#632)
* Fix mobile app to fetch all transactions with pagination

The mobile app was only fetching 25 transactions per account because:
1. TransactionsService didn't pass pagination parameters to the API
2. The backend defaults to 25 records per page when no per_page is specified
3. SyncService didn't implement pagination to fetch all pages

Changes:
- Updated TransactionsService.getTransactions() to accept page and perPage parameters
- Modified the method to extract and return pagination metadata from API response
- Updated SyncService.syncFromServer() to fetch all pages (up to 100 per page)
- Added pagination loop to continue fetching until all pages are retrieved
- Enhanced logging to show pagination progress

This ensures users see all their transactions in the mobile app, not just the first 25.

* Add clear local data feature and enhanced sync logging

Added features:
1. Clear Local Data button in Settings
   - Allows users to clear all cached transactions and accounts
   - Shows confirmation dialog before clearing
   - Displays success/error feedback

2. Enhanced sync logging for debugging
   - Added detailed logs in syncFromServer to track pagination
   - Shows page-by-page progress with transaction counts
   - Logs pagination metadata (total pages, total count, etc.)
   - Tracks upsert progress every 50 transactions
   - Added clear section markers for easier log reading

3. Simplified upsertTransactionFromServer logging
   - Removed verbose debug logs to reduce noise
   - Keeps only essential error/warning logs

This will help users troubleshoot sync issues by:
- Clearing stale data and forcing a fresh sync
- Providing detailed logs to identify where sync might fail

* Fix transaction accountId parsing from API response

The mobile app was only showing 25 transactions per account because:
- The backend API returns account info in nested format: {"account": {"id": "xxx"}}
- The mobile Transaction model expected flat format: {"account_id": "xxx"}
- When parsing, accountId was always empty, so database queries by account_id returned incomplete results

Changes:
1. Updated Transaction.fromJson to handle both formats:
   - New format: {"account": {"id": "xxx", "name": "..."}}
   - Old format: {"account_id": "xxx"} (for backward compatibility)

2. Fixed classification/nature field parsing:
   - Backend sends "classification" field (income/expense)
   - Mobile uses "nature" field
   - Now handles both fields correctly

3. Added debug logging to identify empty accountId issues:
   - Logs first transaction's accountId when syncing
   - Counts and warns about transactions with empty accountId
   - Shows critical errors when trying to save with empty accountId

This ensures all transactions from the server are correctly associated with their accounts in the local database.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 09:27:39 +01:00
Lazy Bone
19da11ed4e This commit adds two new features to the More tab in the mobile app: (#612)
Calendar View:

Monthly calendar displaying daily balance changes for selected account
Account selection dropdown to switch between different accounts
Month navigation with previous/next buttons
Visual indicators (green for income, red for expenses)
Monthly profit/loss summary at the top
Auto-calculation of daily transaction totals
Recent Transactions:

View recent N transactions across all accounts
Configurable display limit (10/20/50/100 transactions)
Pull-to-refresh functionality
Transaction details including account name, date, amount, and notes
Visual distinction between income and expenses
Sorted by date (most recent first)
Implementation details:

Created MoreScreen as menu hub for new features
Replaced PlaceholderScreen with functional MoreScreen
Leverages existing Provider pattern for state management
Uses offline-first approach with existing data providers
Full Chinese localization
Material Design 3 compliant UI
Files added:

mobile/lib/screens/more_screen.dart
mobile/lib/screens/calendar_screen.dart
mobile/lib/screens/recent_transactions_screen.dart
Files modified:

mobile/lib/screens/main_navigation_screen.dart

Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>
Co-authored-by: dwvwdv <dwvwdv@protonmail.com>
2026-01-11 12:57:38 +01:00
Lazy Bone
f52b3fceb6 feat: implement mobile AI chat feature and fix duplicate response issue (#610)
Backend fixes:
- Fix duplicate AssistantResponseJob triggering causing duplicate AI responses
- UserMessage model already handles job triggering via after_create_commit callback
- Remove redundant job enqueue in chats_controller and messages_controller

Mobile app features:
- Implement complete AI chat interface and conversation management
- Add Chat, Message, and ToolCall data models
- Add ChatProvider for state management with polling mechanism
- Add ChatService to handle all chat-related API requests
- Add chat list screen (ChatListScreen)
- Add conversation detail screen (ChatConversationScreen)
- Refactor navigation structure with bottom navigation bar (MainNavigationScreen)
- Add settings screen (SettingsScreen)
- Optimize TransactionsProvider to support account filtering

Technical details:
- Implement message polling mechanism for real-time AI responses
- Support chat creation, deletion, retry and other operations
- Integrate Material Design 3 design language
- Improve user experience and error handling

Co-authored-by: dwvwdv <dwvwdv@protonmail.com>
2026-01-11 12:45:33 +01:00
Lazy Bone
3dfa569bed Mobile offline features (#580)
* feat: mobile support.

Basic functionality development includes adding and deleting transactions,viewing balances,

* Fix mobile support issues in PR #426

This commit addresses the critical issues identified in the mobile-support PR:

1. **GitHub Actions Workflow Path Issues (Critical)**
   - Add mobile/ prefix to all path filters in flutter-build.yml
   - Add working-directory to all Flutter commands
   - Fix Android keystore and iOS CocoaPods paths
   - Fix artifact upload paths

2. **Error Handling Improvements**
   - Add try-catch blocks to all HTTP requests in services
   - Wrap all JSON parsing operations in error handling
   - Add proper error messages for network failures

3. **HTTP Request Timeout Configuration**
   - Add 30-second timeout to all HTTP requests
   - Prevents hanging on network failures

4. **Defensive Null Checks in Providers**
   - Add containsKey() checks before accessing result maps
   - Add proper type casting with null safety
   - Add fallback error messages

These changes ensure the workflow triggers correctly on mobile/ directory
changes and improves overall code robustness.

* Fix transactions exposure and error handling issues

- Add UnmodifiableListView to transactions getter to prevent external mutation
- Call notifyListeners() immediately after setting _isLoading = false
- Move jsonDecode to run only after successful statusCode verification
- Replace string concatenation with Uri.replace() for proper URL encoding
- Add try/catch for jsonDecode on non-2xx responses to handle non-JSON errors

* Fix exception handling and duplicate parsing in auth_service.dart

- Replace broad catch-all exception handlers with targeted exception handling
- Add specific catches for SocketException, TimeoutException, HttpException, FormatException, and TypeError
- Return safe, user-friendly error messages instead of exposing internal details
- Log full exception details and stack traces using debugPrint for debugging
- Fix duplicate User.fromJson calls in login and signup methods by parsing once and reusing the instance
- Improve code efficiency and security by preventing information leakage

* Fix 2FA login crash and improve UX

Fixed the crash that occurred when logging in with 2FA-enabled accounts
and improved the user experience by not showing error messages when MFA
is required (it's a normal flow, not an error).

Changes:
- Added mounted check before setState() in login screen
- Modified AuthProvider to not set error message when MFA is required
- Ensures smooth transition from password entry to OTP entry
- Prevents "setState() called after dispose()" error

The flow now works correctly:
1. User enters email/password → clicks Sign In
2. Backend responds with mfa_required
3. OTP input field appears with friendly blue prompt (no red error)
4. User enters 6-digit code → clicks Sign In again
5. Login succeeds

* Add debug logs to trace 2FA login flow

Added comprehensive debug logging to understand why OTP field
is not showing when MFA is required:
- Log backend response status and body
- Log login result in AuthProvider
- Log MFA required state
- Log when OTP field should be shown

This will help identify if the issue is:
1. Backend not returning mfa_required flag
2. Response parsing issue
3. State management issue
4. UI rendering issue

* Fix 2FA login flow by moving MFA state to AuthProvider

PROBLEM:
The LoginScreen was being recreated when AuthProvider called notifyListeners(),
causing all internal state (_showOtpField) to be lost. This resulted in the
OTP input field never appearing, making 2FA login impossible.

ROOT CAUSE:
The AppWrapper uses a Consumer<AuthProvider> that rebuilds the entire widget
tree when auth state changes. When login() sets isLoading=false and calls
notifyListeners(), a brand new LoginScreen instance is created, resetting
all internal state.

SOLUTION:
- Moved _showMfaInput state from LoginScreen to AuthProvider
- AuthProvider now manages when to show the MFA input field
- LoginScreen uses Consumer to read this state reactively
- State survives widget rebuilds

FLOW:
1. User enters email/password → clicks Sign In
2. Backend responds with mfa_required: true
3. AuthProvider sets _showMfaInput = true
4. Consumer rebuilds, showing OTP field (state preserved)
5. User enters code → clicks Sign In
6. Backend validates → returns tokens → login succeeds

Backend is confirmed working via tests (auth_controller_test.rb).

* Fix mobile 2FA login requiring double password entry

Problem:
When 2FA is required during mobile login, the LoginScreen was being
destroyed and recreated, causing text controllers to reset and forcing
users to re-enter their credentials.

Root cause:
AppWrapper was checking authProvider.isLoading and showing a full-screen
loading indicator during login attempts. This caused LoginScreen to be
unmounted when isLoading=true, destroying the State and text controllers.
When the backend returned mfa_required, isLoading=false triggered
recreation of LoginScreen with empty fields.

Solution:
- Add isInitializing state to AuthProvider to distinguish initial auth
  check from active login attempts
- Update AppWrapper to only show loading spinner during isInitializing,
  not during login flow
- LoginScreen now persists across login attempts, preserving entered
  credentials

Flow after fix:
1. User enters email/password
2. LoginScreen stays mounted (shows loading in button only)
3. Backend returns mfa_required
4. MFA field appears, email/password fields retain values
5. User enters OTP and submits (email/password automatically included)

Files changed:
- mobile/lib/providers/auth_provider.dart: Add isInitializing state
- mobile/lib/main.dart: Use isInitializing instead of isLoading in AppWrapper

* Add OTP error feedback for mobile 2FA login

When users enter an incorrect OTP code during 2FA login, the app now:
- Displays an error message indicating the code was invalid
- Keeps the MFA input field visible for retry
- Automatically clears the OTP field for easy re-entry

Changes:
- mobile/lib/providers/auth_provider.dart:
  * Distinguish between first MFA request vs invalid OTP error
  * Show error message when OTP code was submitted but invalid
  * Keep MFA input visible when in MFA flow with errors

- mobile/lib/screens/login_screen.dart:
  * Clear OTP field after failed login attempt
  * Improve UX by allowing easy retry without re-entering credentials

User flow after fix:
1. User enters email/password
2. MFA required - OTP field appears
3. User enters wrong OTP
4. Error message shows "Two-factor authentication required"
5. OTP field clears, ready for new code
6. User can immediately retry without re-entering email/password

* Improve OTP error message clarity

When user enters an invalid OTP code, show clearer error message
"Invalid authentication code. Please try again." instead of the
confusing "Two-factor authentication required" from backend.

This makes it clear that the OTP was wrong, not that they need to
start the 2FA process.

* chore: delete generation ai create test flow md.

* Update mobile/lib/screens/login_screen.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* feat: add pubspec.lock file.

* Linter

* Update mobile/android/app/build.gradle

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/android/app/build.gradle

com.sure.mobile -> am.sure.mobile

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/ios/Runner.xcodeproj/project.pbxproj

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/ios/Runner.xcodeproj/project.pbxproj

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/ios/Runner.xcodeproj/project.pbxproj

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Fix iOS deployment target and update documentation

- Update iOS minimum deployment target from 12.0 to 13.0 in Podfile for Flutter compatibility
- Translate SIGNING_SETUP.md from Chinese to English for better accessibility
- Remove TECHNICAL_GUIDE.md as requested

* Restore TECHNICAL_GUIDE.md with partial content removal

- Restore mobile/docs/TECHNICAL_GUIDE.md (previously deleted)
- Remove only License, Contributing, and Related Links sections (from line 445 onwards)
- Keep all technical documentation content (lines 1-444)

* Fix setState after dispose errors across mobile app

This commit fixes 5 critical setState/dispose errors identified by Cursor:

1. backend_config_screen.dart: Add mounted checks in _testConnection()
   and _saveAndContinue() methods to prevent setState calls after async
   operations (http.get, SharedPreferences) when widget is disposed.

2. transaction_form_screen.dart: Add mounted check in _selectDate()
   after showDatePicker to prevent setState when modal is dismissed
   while date picker is open.

3. main.dart: Add mounted check in _checkBackendConfig() after
   ApiConfig.initialize() to handle disposal during async initialization.

4. transactions_list_screen.dart: Add mounted check in the .then()
   callback of _showAddTransactionForm() to prevent calling
   _loadTransactions() on a disposed widget when modal is closed.

5. transactions_provider.dart: Fix premature notifyListeners() by
   removing intermediate notification after _isLoading = false,
   ensuring listeners only get notified once with complete state updates
   to prevent momentary stale UI state.

All setState calls after async operations now properly check mounted
status to prevent "setState() called after dispose()" errors.

* Fix Android build: Remove package attribute from AndroidManifest.xml

Remove deprecated package attribute from AndroidManifest.xml. The namespace
is now correctly defined only in build.gradle as required by newer versions
of Android Gradle Plugin.

This fixes the build error:
"Incorrect package="com.sure.mobile" found in source AndroidManifest.xml.
Setting the namespace via the package attribute in the source
AndroidManifest.xml is no longer supported."

* Update issue templates

* Change package name from com.sure.mobile to am.sure.mobile

Updated Android package name across all files:
- build.gradle: namespace and applicationId
- MainActivity.kt: package declaration and file path
- Moved MainActivity.kt from com/sure/mobile to am/sure/mobile

This aligns with the package name change made in the mobile-support branch
and fixes app crashes caused by package name mismatch.

* Fix mobile app code quality issues

- Add mounted check in backend_config_screen.dart to prevent setState after dispose
- Translate Chinese comments to English in transactions_list_screen.dart for better maintainability
- Replace brittle string-split date conversion with DateFormat in transaction_form_screen.dart for safer date handling

These changes address code review feedback and improve code robustness.

* Remove feature request template

Delete unused feature request issue template file.

* Fix mobile app code quality issues

- Fix URL construction in backend_config_screen.dart to prevent double slashes by normalizing base URL (removing trailing slashes) before appending paths
- Update pubspec.yaml to require Flutter 3.27.0+ for withValues API compatibility
- Improve amount parsing robustness in transactions_list_screen.dart with proper locale handling, sign detection, and fallback error handling
- Fix dismissible delete handler to prevent UI/backend inconsistency by moving deletion to confirmDismiss and only allowing dismissal on success

* Fix mobile app performance and security issues

- Eliminate duplicate _getAmountDisplayInfo calls in transactions list by computing display info once per transaction item
- Upgrade flutter_secure_storage from 9.0.0 to 10.0.0 for AES-GCM encryption
- Update dev dependencies: flutter_lints to 6.0.0 and flutter_launcher_icons to 0.14.4

* Update Android SDK requirements for flutter_secure_storage v10

- Increase compileSdk from 35 to 36
- Increase minSdkVersion from 21 to 24

This is required by flutter_secure_storage v10+ which uses newer Android APIs for AES-GCM encryption.

* Fix transaction deletion message not displaying properly

The success message was being shown in the onDismissed callback, which
executes after the dismissal animation completes. By that time, the
context may have become invalid due to widget tree rebuilds, causing
the SnackBar to not display.

Moved the success message to the confirmDismiss callback where we
already have a captured scaffoldMessenger reference, ensuring the
message displays reliably before the dismissal animation begins.

* Add mounted check before showing SnackBar after async operation

* Update mobile/android/app/build.gradle

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Fix empty state refresh and auth error feedback in mobile transactions screen

- Wrap empty state in RefreshIndicator with CustomScrollView to enable pull-to-refresh when no transactions exist
- Wrap error state in RefreshIndicator as well for consistency
- Add SnackBar feedback when auth token is null in _loadTransactions instead of silent failure
- Ensure mounted check before showing SnackBar to prevent errors after widget disposal

* Fix flash of 'No accounts yet' page on app startup

Added initialization state tracking to AccountsProvider to prevent
the empty state from briefly showing while accounts are being loaded
for the first time.

Changes:
- Add _isInitializing flag to AccountsProvider (starts as true)
- Set to false after first fetchAccounts() completes
- Reset to true when clearAccounts() is called
- Update DashboardScreen to show loading during initialization

This ensures a smooth user experience without visual flashing on app launch.

* Refactor: Extract transaction deletion logic into dedicated method

Improved code readability by extracting the 67-line confirmDismiss
callback into a separate _confirmAndDeleteTransaction method.

Changes:
- Add Transaction model import
- Create _confirmAndDeleteTransaction method that handles:
  - Confirmation dialog
  - Token retrieval
  - Deletion API call
  - Success/failure feedback
- Simplify confirmDismiss to single line calling new method

This separation of concerns makes the code more maintainable and
the Dismissible widget configuration more concise.

* Add offline-first architecture for mobile app

Implement comprehensive offline functionality for the Sure mobile app:

- SQLite local storage for transactions and accounts
- Network connectivity detection and monitoring
- Offline transaction creation with pending sync status
- Automatic synchronization when network is restored
- Visual indicators for offline status and pending syncs

New Services:
- ConnectivityService: Real-time network status monitoring
- DatabaseHelper: SQLite database management with schema
- OfflineStorageService: High-level API for local data operations
- SyncService: Bidirectional sync between local DB and server

New Models:
- OfflineTransaction: Extended transaction model with sync status
- SyncStatus enum: Track transaction sync state (synced/pending/failed)

New UI Components:
- ConnectivityBanner: Shows offline status and sync actions
- SyncStatusBadge: Displays sync status on transactions

Updated Components:
- TransactionsProvider: Offline-first data fetching and caching
- Main app: Wire up connectivity and sync services
- Dashboard: Display connectivity banner

Dependencies Added:
- sqflite: SQLite database
- connectivity_plus: Network status detection
- uuid: Generate local transaction IDs
- path: Database path utilities

The app now works seamlessly offline, saving all transactions locally
and automatically syncing when connectivity is restored.

* Fix lint errors and warnings in offline features

- Fix connectivity_service to handle ConnectivityResult (not List)
- Remove unused imports in database_helper
- Replace deprecated withOpacity with withValues in sync_status_badge
- Use super parameters in OfflineTransaction constructor

* Add offline support for AccountsProvider

- Implement offline-first architecture for accounts
- Load cached accounts from SQLite first for instant display
- Sync with server when online and save to cache
- Wire up ConnectivityService to AccountsProvider
- Show cached data even when offline

This fixes the issue where accounts couldn't be loaded offline.

* Add debug logging to track offline transaction sync issues

- Add detailed logging in TransactionsProvider.fetchTransactions
- Add logging in SyncService.syncFromServer
- Track transaction counts at each step
- Log server responses and database operations

This will help identify why transaction lists are empty.

* Fix critical offline sync issues and add auto-sync

Major fixes:
1. Fix accountId missing issue when syncing from server
   - Server may not return account_id in transaction JSON
   - Now use provided accountId parameter as fallback
   - Ensures transactions are saved with correct account_id

2. Add automatic sync when connectivity restored
   - Store last access token for auto-sync
   - Automatically upload pending transactions when back online
   - Prevent duplicate auto-sync attempts

3. Enhanced debug logging
   - Track accountId from server vs provided parameter
   - Log transaction upsert operations
   - Better error tracking

This fixes:
- Empty transaction lists when viewing account details
- Offline transactions not auto-syncing when online
- Data not being properly saved to local database

* Add comprehensive debug logging for transaction storage

Added detailed logging to track:
- Account ID values from server vs provided parameter
- Effective account ID being used for storage
- Database insert/update operations
- Number of transactions retrieved from database
- Sample account IDs from stored transactions

This will help identify:
1. Whether server returns account_id in transaction JSON
2. If fallback accountId parameter is being used correctly
3. What values are actually stored in the database
4. Why queries might not be finding transactions

* Add detailed database query logging

Added SQL query logging to DatabaseHelper:
- Log all INSERT operations with account_id values
- Log SELECT queries with WHERE conditions
- Log result counts from queries

This will show:
- Exact account_id values being stored in database
- SQL WHERE clauses being used for queries
- Whether queries are finding matching records

Combined with OfflineStorageService logs, this gives complete
visibility into the data flow from server to database to UI.

* Add debug log viewer screen

- Create LogService for centralized log management
- Add LogViewerScreen with filtering and export capabilities
- Add debug logs button to dashboard
- Support log levels: ERROR, WARNING, INFO, DEBUG
- Features: filter by level, auto-scroll, copy to clipboard, clear logs
- Keep last 1000 log entries

* Add visual indicators for offline transactions and sync success

Implemented the following features:
1. Pending transactions are now displayed with 50% opacity (gray)
2. Sync status badges (orange sync icon) show next to pending transaction amounts
3. Green cloud icon appears in dashboard AppBar when sync completes successfully
4. Auto-hide sync success indicator after 3 seconds with fade animation
5. Transaction list now uses offlineTransactions to access sync status

Changes:
- transactions_list_screen.dart: Display pending status with opacity and badges
- dashboard_screen.dart: Add sync success indicator with listener
- transactions_provider.dart: Already had offline-first architecture from previous commit

* Fix offline transaction creation and add comprehensive logging

This commit fixes all three reported issues:

1. Log viewer now showing logs
   - Added test log when log viewer opens
   - Added LogService to all critical services
   - Comprehensive logging throughout the stack

2. Offline transaction creation now working
   - TransactionFormScreen now uses TransactionsProvider (offline-first)
   - Previously was calling HTTP API directly, bypassing offline storage
   - Transactions now save to SQLite immediately, then sync in background

3. Sync and cloud icon improvements
   - Dashboard already has sync success indicator from previous commit
   - Added connectivity logging to track online/offline state changes
   - Sync will trigger automatically when connectivity restored

Changes:
- transaction_form_screen.dart: Use TransactionsProvider instead of TransactionsService
- offline_storage_service.dart: Add comprehensive logging with LogService
- connectivity_service.dart: Add logging for connectivity state changes
- log_viewer_screen.dart: Add test log on screen open
- main.dart: Add app startup log

All operations now properly logged and can be viewed in the debug log viewer.

* Fix transaction disappearing after sync and add manual sync

Critical fixes:
1. Fixed syncTransactionsFromServer deleting uploaded transactions
   - Changed clearTransactions() to clearSyncedTransactions()
   - Now only clears synced transactions, preserves pending/failed ones
   - Added comprehensive logging to track sync process

2. Converted dashboard refresh button to manual sync
   - Now triggers full sync: upload pending, download from server
   - Shows progress indicator during sync
   - Displays success/error messages

Technical details:
- database_helper.dart: Added clearSyncedTransactions() method
- offline_storage_service.dart: Use clearSyncedTransactions() in syncTransactionsFromServer()
- sync_service.dart: Added detailed logging to all sync operations
- dashboard_screen.dart: Refresh button now calls performManualSync()

Previous issue:
When connectivity restored, pending transactions were uploaded successfully,
but then syncTransactionsFromServer() would call clearTransactions() which
deleted ALL transactions including the just-uploaded ones, causing them to
disappear from the UI even though they were on the server.

Now fixed by only clearing synced transactions, keeping pending ones safe.

* Change sync to use upsert logic instead of clear+insert

Critical fix: syncTransactionsFromServer now uses upsert for each transaction
instead of clearing synced transactions first. This prevents the race condition
where:
1. Pending transaction uploaded and marked as synced
2. syncTransactionsFromServer clears all synced transactions (including just uploaded)
3. Server response doesn't include the new transaction yet
4. Transaction disappears from local database

Changes:
- offline_storage_service.dart: syncTransactionsFromServer now calls
  upsertTransactionFromServer for each transaction
- Added logic to preserve existing accountId if server response has empty accountId
- Replaced debugPrint with LogService for better tracking
- Added detailed logging to track upsert operations

This ensures uploaded transactions are never deleted, only updated with
server data when available.

* Fix flutter analyze warnings

- Remove unused import 'package:flutter/foundation.dart' from offline_storage_service.dart
- Fix use_build_context_synchronously warnings in transactions_list_screen.dart
  by capturing providers before async gap (showDialog)

* Fix offline transaction sync not uploading pending transactions

Critical bug: performFullSync was calling syncPendingTransactions which
immediately returned 'Sync already in progress' error because performFullSync
had already set _isSyncing = true.

This caused pending transactions to never be uploaded to the server.

Solution:
- Extract core upload logic into _syncPendingTransactionsInternal() method
- This internal method doesn't check _isSyncing flag
- performFullSync calls the internal method directly
- Public syncPendingTransactions() still checks _isSyncing for standalone calls

Now pending transactions will properly upload during full sync.

* Initial plan

* Fix lifecycle, logging, error handling, and accessibility issues

- Fixed provider lifecycle in dashboard_screen.dart to prevent accessing disposed providers
- Fixed connectivity listener tracking in transactions_provider.dart with proper cleanup
- Fixed async callback race conditions by checking mounted state
- Replaced all debugPrint with LogService for consistent production logging
- Added comprehensive error handling for database initialization with FlutterError reporting
- Added missing index on server_id column for improved query performance
- Optimized LogService memory usage (reduced from 1000 to 500 max logs)
- Added debounced notifications in LogService (only when log viewer is active)
- Added semantic labels to sync status badge icons for accessibility
- Added semantic label to debug logs button for screen readers
- Fixed misleading success message in transaction form (conditional based on sync status)
- Fixed fire-and-forget async error handling to properly surface errors to UI
- Improved error messages in accounts_provider with specific error types

Co-authored-by: dwvwdv <89256478+dwvwdv@users.noreply.github.com>

* Fix semantic labels and connectivity check issues from code review

- Added semantic label to non-compact sync status badge for consistency
- Fixed connectivity check in transaction form to use actual ConnectivityService.isOnline instead of lastSyncTime

Co-authored-by: dwvwdv <89256478+dwvwdv@users.noreply.github.com>

* Fix mounted check to properly track provider disposal state

- Added _isDisposed flag to track actual provider disposal
- Updated mounted getter to use _isDisposed instead of connectivity service availability
- Ensures disposed providers don't continue processing connectivity changes

Co-authored-by: dwvwdv <89256478+dwvwdv@users.noreply.github.com>

* Address code review nitpicks for better code quality

- Improved exception type checking in accounts_provider using instanceof instead of string contains
- Improved ternary operator formatting for better readability
- Added clarifying comment for fire-and-forget async pattern in connectivity change handler

Co-authored-by: dwvwdv <89256478+dwvwdv@users.noreply.github.com>

* Fix exception handling, error messages, and database index

- Removed HandshakeException instanceof check, use string-based check for SSL/certificate errors
- Improved error message clarity: 'Transaction upload failed' instead of 'Background sync failed'
- Optimized server_id index to exclude NULL values for better performance and reduced size

Co-authored-by: dwvwdv <89256478+dwvwdv@users.noreply.github.com>

* Remove partial index syntax for better SQLite compatibility

- Changed server_id index to standard syntax without WHERE clause
- SQLite handles NULL values efficiently without explicit exclusion
- Ensures compatibility across all SQLite versions

Co-authored-by: dwvwdv <89256478+dwvwdv@users.noreply.github.com>

* Fix Android release build to work without keystore

- Made signing config optional when key.properties is missing
- Prevents null pointer exception during release build
- APK will be unsigned if no keystore is configured (debug signing will be used)

Co-authored-by: dwvwdv <89256478+dwvwdv@users.noreply.github.com>

* Improve keystore validation in build.gradle

Enhanced keystore validation by checking keyAlias, keyPassword, and storePassword in addition to storeFile existence.

Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Enhance Flutter build workflow with keystore checks

Added checks for keystore secrets and adjusted APK build process based on their presence.

Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Revert "Improve keystore validation in build.gradle"

* Update mobile/lib/services/connectivity_service.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/lib/services/sync_service.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Fix mobile provider null handling and connectivity issues

- Fix null handling in ChangeNotifierProxyProvider callbacks for AccountsProvider and TransactionsProvider
- Change connectivity service fallback from online to offline to prevent unwanted network calls
- Replace raw error messages with user-friendly messages in TransactionsProvider
- Add VPN and Bluetooth to online connectivity check
- Fix database close() method to properly reset static cache
- Add loading state and error handling to connectivity banner sync button
- Update dependencies to latest stable versions (sqflite 2.4.2, path 1.9.1, connectivity_plus 7.0.0, uuid 4.5.2)

* Replace all debugPrint with LogService in mobile app

- Replace debugPrint with LogService in transactions_list_screen.dart
- Replace debugPrint with LogService in auth_provider.dart
- Replace debugPrint with LogService in auth_service.dart (login, signup, refreshToken methods)
- Add LogService imports to all affected files
- Use appropriate log levels: debug for informational logs, error for exceptions
- Keep debugPrint only in LogService internal implementation

All debugPrint calls now properly use the centralized LogService for consistent logging.

* Fix Flutter analyze errors and warnings

- Remove unused flutter/foundation.dart import in auth_service.dart
- Update connectivity_service.dart to handle List<ConnectivityResult> for connectivity_plus 7.0.0
  - Change StreamSubscription type from ConnectivityResult to List<ConnectivityResult>
  - Update _updateConnectionStatus to handle list of results using .any()
  - Check if any connectivity result indicates online status (mobile, wifi, ethernet, vpn, bluetooth)
- Fix BuildContext usage across async gaps in connectivity_banner.dart
  - Use immediate mounted check before context usage (if (!mounted) return)
  - Properly guard all ScaffoldMessenger calls after async operations

All flutter analyze errors and info warnings resolved.

* Update mobile/lib/providers/accounts_provider.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/lib/providers/transactions_provider.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/lib/screens/dashboard_screen.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/lib/screens/dashboard_screen.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Add error logging and implement pending delete tracking for offline transactions

This commit adds comprehensive error logging and implements a robust pending
delete system for offline transactions, ensuring data integrity and proper
synchronization.

Changes:
- Add LogService error logging in AccountsProvider and DashboardScreen
- Add SyncStatus.pendingDelete enum for tracking offline deletions
- Implement markTransactionForDeletion in OfflineStorageService
- Update offline delete logic to mark transactions instead of immediate deletion
- Add _syncPendingDeletesInternal to SyncService for processing pending deletes
- Update performFullSync to process pending deletes before uploads
- Update hasPendingTransactions and pendingCount to include pending deletes

The pending delete system ensures that:
- Offline deletions are tracked and synced when connectivity is restored
- Transactions with server IDs are deleted from both local and server
- Transactions without server IDs (never synced) are deleted locally only
- Failed delete attempts are marked and retried on next sync

* Fix Flutter analyzer issues for pending delete feature

- Add SyncStatus.pendingDelete case to sync_status_badge.dart switch
- Use const constructor for SnackBar in dashboard_screen.dart
- Display "Deleting" badge with delete icon for pendingDelete status

* Add undo functionality for pending transactions

This commit adds the ability to undo pending operations (both creates and
deletes) while offline, providing users with better control over their
pending transactions.

Features:
- Gray out pending delete transactions (50% opacity) similar to pending creates
- Add "Undo" button on all pending transactions (both pending and pendingDelete)
- Implement undoPendingTransaction in OfflineStorageService:
  - Pending creates: delete the transaction from local storage
  - Pending deletes: restore transaction to synced status
- Add undoPendingTransaction method to TransactionsProvider
- Show appropriate confirmation dialogs for undo operations
- Display success/error messages after undo attempts

User experience:
- Pending transactions are visually distinct (grayed out with status badge)
- Users can easily revert offline actions before they sync
- Clear feedback on undo success or failure

---------

Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>
Co-authored-by: dwvwdv <dwvwdv@protonmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-01-11 10:29:23 +01:00
Lazy Bone
7866598057 Mobile native client via Flutter (#426)
* feat: mobile support.

Basic functionality development includes adding and deleting transactions,viewing balances,

* Fix mobile support issues in PR #426

This commit addresses the critical issues identified in the mobile-support PR:

1. **GitHub Actions Workflow Path Issues (Critical)**
   - Add mobile/ prefix to all path filters in flutter-build.yml
   - Add working-directory to all Flutter commands
   - Fix Android keystore and iOS CocoaPods paths
   - Fix artifact upload paths

2. **Error Handling Improvements**
   - Add try-catch blocks to all HTTP requests in services
   - Wrap all JSON parsing operations in error handling
   - Add proper error messages for network failures

3. **HTTP Request Timeout Configuration**
   - Add 30-second timeout to all HTTP requests
   - Prevents hanging on network failures

4. **Defensive Null Checks in Providers**
   - Add containsKey() checks before accessing result maps
   - Add proper type casting with null safety
   - Add fallback error messages

These changes ensure the workflow triggers correctly on mobile/ directory
changes and improves overall code robustness.

* Fix transactions exposure and error handling issues

- Add UnmodifiableListView to transactions getter to prevent external mutation
- Call notifyListeners() immediately after setting _isLoading = false
- Move jsonDecode to run only after successful statusCode verification
- Replace string concatenation with Uri.replace() for proper URL encoding
- Add try/catch for jsonDecode on non-2xx responses to handle non-JSON errors

* Fix exception handling and duplicate parsing in auth_service.dart

- Replace broad catch-all exception handlers with targeted exception handling
- Add specific catches for SocketException, TimeoutException, HttpException, FormatException, and TypeError
- Return safe, user-friendly error messages instead of exposing internal details
- Log full exception details and stack traces using debugPrint for debugging
- Fix duplicate User.fromJson calls in login and signup methods by parsing once and reusing the instance
- Improve code efficiency and security by preventing information leakage

* Fix 2FA login crash and improve UX

Fixed the crash that occurred when logging in with 2FA-enabled accounts
and improved the user experience by not showing error messages when MFA
is required (it's a normal flow, not an error).

Changes:
- Added mounted check before setState() in login screen
- Modified AuthProvider to not set error message when MFA is required
- Ensures smooth transition from password entry to OTP entry
- Prevents "setState() called after dispose()" error

The flow now works correctly:
1. User enters email/password → clicks Sign In
2. Backend responds with mfa_required
3. OTP input field appears with friendly blue prompt (no red error)
4. User enters 6-digit code → clicks Sign In again
5. Login succeeds

* Add debug logs to trace 2FA login flow

Added comprehensive debug logging to understand why OTP field
is not showing when MFA is required:
- Log backend response status and body
- Log login result in AuthProvider
- Log MFA required state
- Log when OTP field should be shown

This will help identify if the issue is:
1. Backend not returning mfa_required flag
2. Response parsing issue
3. State management issue
4. UI rendering issue

* Fix 2FA login flow by moving MFA state to AuthProvider

PROBLEM:
The LoginScreen was being recreated when AuthProvider called notifyListeners(),
causing all internal state (_showOtpField) to be lost. This resulted in the
OTP input field never appearing, making 2FA login impossible.

ROOT CAUSE:
The AppWrapper uses a Consumer<AuthProvider> that rebuilds the entire widget
tree when auth state changes. When login() sets isLoading=false and calls
notifyListeners(), a brand new LoginScreen instance is created, resetting
all internal state.

SOLUTION:
- Moved _showMfaInput state from LoginScreen to AuthProvider
- AuthProvider now manages when to show the MFA input field
- LoginScreen uses Consumer to read this state reactively
- State survives widget rebuilds

FLOW:
1. User enters email/password → clicks Sign In
2. Backend responds with mfa_required: true
3. AuthProvider sets _showMfaInput = true
4. Consumer rebuilds, showing OTP field (state preserved)
5. User enters code → clicks Sign In
6. Backend validates → returns tokens → login succeeds

Backend is confirmed working via tests (auth_controller_test.rb).

* Fix mobile 2FA login requiring double password entry

Problem:
When 2FA is required during mobile login, the LoginScreen was being
destroyed and recreated, causing text controllers to reset and forcing
users to re-enter their credentials.

Root cause:
AppWrapper was checking authProvider.isLoading and showing a full-screen
loading indicator during login attempts. This caused LoginScreen to be
unmounted when isLoading=true, destroying the State and text controllers.
When the backend returned mfa_required, isLoading=false triggered
recreation of LoginScreen with empty fields.

Solution:
- Add isInitializing state to AuthProvider to distinguish initial auth
  check from active login attempts
- Update AppWrapper to only show loading spinner during isInitializing,
  not during login flow
- LoginScreen now persists across login attempts, preserving entered
  credentials

Flow after fix:
1. User enters email/password
2. LoginScreen stays mounted (shows loading in button only)
3. Backend returns mfa_required
4. MFA field appears, email/password fields retain values
5. User enters OTP and submits (email/password automatically included)

Files changed:
- mobile/lib/providers/auth_provider.dart: Add isInitializing state
- mobile/lib/main.dart: Use isInitializing instead of isLoading in AppWrapper

* Add OTP error feedback for mobile 2FA login

When users enter an incorrect OTP code during 2FA login, the app now:
- Displays an error message indicating the code was invalid
- Keeps the MFA input field visible for retry
- Automatically clears the OTP field for easy re-entry

Changes:
- mobile/lib/providers/auth_provider.dart:
  * Distinguish between first MFA request vs invalid OTP error
  * Show error message when OTP code was submitted but invalid
  * Keep MFA input visible when in MFA flow with errors

- mobile/lib/screens/login_screen.dart:
  * Clear OTP field after failed login attempt
  * Improve UX by allowing easy retry without re-entering credentials

User flow after fix:
1. User enters email/password
2. MFA required - OTP field appears
3. User enters wrong OTP
4. Error message shows "Two-factor authentication required"
5. OTP field clears, ready for new code
6. User can immediately retry without re-entering email/password

* Improve OTP error message clarity

When user enters an invalid OTP code, show clearer error message
"Invalid authentication code. Please try again." instead of the
confusing "Two-factor authentication required" from backend.

This makes it clear that the OTP was wrong, not that they need to
start the 2FA process.

* chore: delete generation ai create test flow md.

* Update mobile/lib/screens/login_screen.dart

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* feat: add pubspec.lock file.

* Linter

* Update mobile/android/app/build.gradle

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/android/app/build.gradle

com.sure.mobile -> am.sure.mobile

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/ios/Runner.xcodeproj/project.pbxproj

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/ios/Runner.xcodeproj/project.pbxproj

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Update mobile/ios/Runner.xcodeproj/project.pbxproj

Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Fix iOS deployment target and update documentation

- Update iOS minimum deployment target from 12.0 to 13.0 in Podfile for Flutter compatibility
- Translate SIGNING_SETUP.md from Chinese to English for better accessibility
- Remove TECHNICAL_GUIDE.md as requested

* Restore TECHNICAL_GUIDE.md with partial content removal

- Restore mobile/docs/TECHNICAL_GUIDE.md (previously deleted)
- Remove only License, Contributing, and Related Links sections (from line 445 onwards)
- Keep all technical documentation content (lines 1-444)

* Fix setState after dispose errors across mobile app

This commit fixes 5 critical setState/dispose errors identified by Cursor:

1. backend_config_screen.dart: Add mounted checks in _testConnection()
   and _saveAndContinue() methods to prevent setState calls after async
   operations (http.get, SharedPreferences) when widget is disposed.

2. transaction_form_screen.dart: Add mounted check in _selectDate()
   after showDatePicker to prevent setState when modal is dismissed
   while date picker is open.

3. main.dart: Add mounted check in _checkBackendConfig() after
   ApiConfig.initialize() to handle disposal during async initialization.

4. transactions_list_screen.dart: Add mounted check in the .then()
   callback of _showAddTransactionForm() to prevent calling
   _loadTransactions() on a disposed widget when modal is closed.

5. transactions_provider.dart: Fix premature notifyListeners() by
   removing intermediate notification after _isLoading = false,
   ensuring listeners only get notified once with complete state updates
   to prevent momentary stale UI state.

All setState calls after async operations now properly check mounted
status to prevent "setState() called after dispose()" errors.

* Fix Android build: Remove package attribute from AndroidManifest.xml

Remove deprecated package attribute from AndroidManifest.xml. The namespace
is now correctly defined only in build.gradle as required by newer versions
of Android Gradle Plugin.

This fixes the build error:
"Incorrect package="com.sure.mobile" found in source AndroidManifest.xml.
Setting the namespace via the package attribute in the source
AndroidManifest.xml is no longer supported."

* Update issue templates

* Change package name from com.sure.mobile to am.sure.mobile

Updated Android package name across all files:
- build.gradle: namespace and applicationId
- MainActivity.kt: package declaration and file path
- Moved MainActivity.kt from com/sure/mobile to am/sure/mobile

This aligns with the package name change made in the mobile-support branch
and fixes app crashes caused by package name mismatch.

* Fix mobile app code quality issues

- Add mounted check in backend_config_screen.dart to prevent setState after dispose
- Translate Chinese comments to English in transactions_list_screen.dart for better maintainability
- Replace brittle string-split date conversion with DateFormat in transaction_form_screen.dart for safer date handling

These changes address code review feedback and improve code robustness.

* Remove feature request template

Delete unused feature request issue template file.

* Fix mobile app code quality issues

- Fix URL construction in backend_config_screen.dart to prevent double slashes by normalizing base URL (removing trailing slashes) before appending paths
- Update pubspec.yaml to require Flutter 3.27.0+ for withValues API compatibility
- Improve amount parsing robustness in transactions_list_screen.dart with proper locale handling, sign detection, and fallback error handling
- Fix dismissible delete handler to prevent UI/backend inconsistency by moving deletion to confirmDismiss and only allowing dismissal on success

* Fix mobile app performance and security issues

- Eliminate duplicate _getAmountDisplayInfo calls in transactions list by computing display info once per transaction item
- Upgrade flutter_secure_storage from 9.0.0 to 10.0.0 for AES-GCM encryption
- Update dev dependencies: flutter_lints to 6.0.0 and flutter_launcher_icons to 0.14.4

* Update Android SDK requirements for flutter_secure_storage v10

- Increase compileSdk from 35 to 36
- Increase minSdkVersion from 21 to 24

This is required by flutter_secure_storage v10+ which uses newer Android APIs for AES-GCM encryption.

* Fix transaction deletion message not displaying properly

The success message was being shown in the onDismissed callback, which
executes after the dismissal animation completes. By that time, the
context may have become invalid due to widget tree rebuilds, causing
the SnackBar to not display.

Moved the success message to the confirmDismiss callback where we
already have a captured scaffoldMessenger reference, ensuring the
message displays reliably before the dismissal animation begins.

* Add mounted check before showing SnackBar after async operation

* Update mobile/android/app/build.gradle

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Fix empty state refresh and auth error feedback in mobile transactions screen

- Wrap empty state in RefreshIndicator with CustomScrollView to enable pull-to-refresh when no transactions exist
- Wrap error state in RefreshIndicator as well for consistency
- Add SnackBar feedback when auth token is null in _loadTransactions instead of silent failure
- Ensure mounted check before showing SnackBar to prevent errors after widget disposal

* Fix flash of 'No accounts yet' page on app startup

Added initialization state tracking to AccountsProvider to prevent
the empty state from briefly showing while accounts are being loaded
for the first time.

Changes:
- Add _isInitializing flag to AccountsProvider (starts as true)
- Set to false after first fetchAccounts() completes
- Reset to true when clearAccounts() is called
- Update DashboardScreen to show loading during initialization

This ensures a smooth user experience without visual flashing on app launch.

* Refactor: Extract transaction deletion logic into dedicated method

Improved code readability by extracting the 67-line confirmDismiss
callback into a separate _confirmAndDeleteTransaction method.

Changes:
- Add Transaction model import
- Create _confirmAndDeleteTransaction method that handles:
  - Confirmation dialog
  - Token retrieval
  - Deletion API call
  - Success/failure feedback
- Simplify confirmDismiss to single line calling new method

This separation of concerns makes the code more maintainable and
the Dismissible widget configuration more concise.

* Enhance Flutter build workflow with keystore checks

Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

* Implement conditional signing configuration

Added a check for keystore properties before configuring signing.

Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>

---------

Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com>
Co-authored-by: dwvwdv <dwvwdv@protonmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Pedro Piñera Buendía <663605+pepicrft@users.noreply.github.com>
2026-01-08 11:27:31 +01:00