Files
sure/mobile/docs/TECHNICAL_GUIDE.md
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

12 KiB

Sure Mobile - Technical Documentation

Project Overview

Sure Mobile is the mobile application for the Sure Personal Finance Management System, developed with Flutter framework and supporting both Android and iOS platforms. This application provides core mobile functionality for the Sure finance management system, allowing users to view and manage their financial accounts anytime, anywhere.

Backend Relationship

This application is a client app for the Sure Finance Management System and requires connection to the Sure backend server (Rails API) to function properly. Backend project: https://github.com/we-promise/sure

Core Features

1. Backend Configuration

  • Server Address Configuration: Configure Sure backend server URL on first launch
  • Connection Testing: Provides connection test functionality to verify server availability
  • Address Persistence: Server address is saved locally and automatically loaded on next startup

2. User Authentication

  • Login: Support email and password login
  • Two-Factor Authentication (MFA): Support OTP verification code secondary verification
  • User Registration: Support new user registration (backend supported)
  • Token Management:
    • Access Token for API request authentication
    • Refresh Token for refreshing expired Access Tokens
    • Tokens securely stored in device's secure storage
  • Auto-login: Automatically checks local tokens on app startup and logs in if valid
  • Device Information Tracking: Records device information on login for backend session management

3. Account Management

  • Account List Display: Shows all user financial accounts
  • Account Classification:
    • Asset Accounts: Bank accounts, investment accounts, cryptocurrency, real estate, vehicles, etc.
    • Liability Accounts: Credit cards, loans, etc.
    • Other Accounts: Uncategorized accounts
  • Account Type Support:
    • Depository
    • Credit Card
    • Investment
    • Loan
    • Property
    • Vehicle
    • Crypto
    • Other assets/liabilities
  • Balance Display: Shows current balance and currency type for each account
  • Pull to Refresh: Supports pull-to-refresh for account data

Technical Architecture

Tech Stack

  • Framework: Flutter 3.0+
  • Language: Dart 3.0+
  • State Management: Provider
  • Network Requests: http
  • Local Storage:
    • shared_preferences (non-sensitive data, like server URL)
    • flutter_secure_storage (sensitive data, like tokens)

Project Structure

lib/
├── main.dart                      # App entry point
├── models/                        # Data models
│   ├── account.dart              # Account model
│   ├── auth_tokens.dart          # Authentication token model
│   └── user.dart                 # User model
├── providers/                     # State management
│   ├── auth_provider.dart        # Authentication state management
│   └── accounts_provider.dart    # Accounts state management
├── screens/                       # Screens
│   ├── backend_config_screen.dart # Backend configuration screen
│   ├── login_screen.dart         # Login screen
│   └── dashboard_screen.dart     # Main screen (account list)
├── services/                      # Business services
│   ├── api_config.dart           # API configuration
│   ├── auth_service.dart         # Authentication service
│   ├── accounts_service.dart     # Accounts service
│   └── device_service.dart       # Device information service
└── widgets/                       # Reusable widgets
    └── account_card.dart         # Account card widget

Application Flow Details

Startup Flow

App Launch
    ↓
Initialize ApiConfig (load saved backend URL)
    ↓
Check if backend URL is configured
    ├─ No → Show backend configuration screen
    │         ↓
    │       Enter and test URL
    │         ↓
    │       Save configuration
    │         ↓
    └─ Yes → Check Token
            ├─ Invalid or not exists → Show login screen
            │                           ↓
            │                         User login
            │                           ↓
            │                         Save tokens and user info
            │                           ↓
            └─ Valid → Enter Dashboard

Authentication Flow

1. Login Flow (login_screen.dart)

User enters email and password
    ↓
Click login button
    ↓
AuthProvider.login()
    ↓
Collect device information (DeviceService)
    ↓
Call AuthService.login()
    ↓
Send POST /api/v1/auth/login
    ├─ Success (200)
    │   ↓
    │  Save Access Token and Refresh Token
    │   ↓
    │  Save user information
    │   ↓
    │  Navigate to dashboard
    │
    ├─ MFA Required (401 + mfa_required)
    │   ↓
    │  Show OTP input field
    │   ↓
    │  User enters verification code
    │   ↓
    │  Re-login (with OTP)
    │
    └─ Failure
        ↓
       Show error message

2. Token Refresh Flow (auth_provider.dart)

Need to access API
    ↓
Check if Access Token is expired
    ├─ Not expired → Use directly
    │
    └─ Expired
        ↓
       Get Refresh Token
        ↓
       Call AuthService.refreshToken()
        ↓
       Send POST /api/v1/auth/refresh
        ├─ Success
        │   ↓
        │  Save new tokens
        │   ↓
        │  Return new Access Token
        │
        └─ Failure
            ↓
           Clear tokens
            ↓
           Return to login screen

Account Data Flow

1. Fetch Account List (dashboard_screen.dart)

Enter dashboard
    ↓
_loadAccounts()
    ↓
Get valid Access Token from AuthProvider
    ├─ Token invalid
    │   ↓
    │  Logout and return to login screen
    │
    └─ Token valid
        ↓
       AccountsProvider.fetchAccounts()
        ↓
       Call AccountsService.getAccounts()
        ↓
       Send GET /api/v1/accounts
        ├─ Success (200)
        │   ↓
        │  Parse account data
        │   ↓
        │  Group by classification (asset/liability)
        │   ↓
        │  Update UI
        │
        ├─ Unauthorized (401)
        │   ↓
        │  Clear local data
        │   ↓
        │  Return to login screen
        │
        └─ Other errors
            ↓
           Show error message

2. Account Classification Logic (accounts_provider.dart)

// Asset accounts: classification == 'asset'
List<Account> get assetAccounts =>
    accounts.where((a) => a.isAsset).toList();

// Liability accounts: classification == 'liability'
List<Account> get liabilityAccounts =>
    accounts.where((a) => a.isLiability).toList();

// Uncategorized accounts
List<Account> get uncategorizedAccounts =>
    accounts.where((a) => !a.isAsset && !a.isLiability).toList();

UI State Management

The app uses Provider for state management, with two main providers:

AuthProvider (auth_provider.dart)

Manages authentication-related state:

  • isAuthenticated: Whether user is logged in
  • isLoading: Whether loading is in progress
  • user: Current user information
  • errorMessage: Error message
  • mfaRequired: Whether MFA verification is required

AccountsProvider (accounts_provider.dart)

Manages account data state:

  • accounts: All accounts list
  • isLoading: Whether loading is in progress
  • errorMessage: Error message
  • assetAccounts: Asset accounts list
  • liabilityAccounts: Liability accounts list

API Endpoints

The app interacts with the backend through the following API endpoints:

Authentication

  • POST /api/v1/auth/login - User login
  • POST /api/v1/auth/signup - User registration
  • POST /api/v1/auth/refresh - Refresh token

Accounts

  • GET /api/v1/accounts - Get account list (supports pagination)

Health Check

  • GET /sessions/new - Verify backend service availability

Data Models

Account Model

class Account {
  final String id;              // Account ID (UUID)
  final String name;            // Account name
  final String balance;         // Balance (string format)
  final String currency;        // Currency type (e.g., USD, TWD)
  final String? classification; // Classification (asset/liability)
  final String accountType;     // Account type (depository, credit_card, etc.)
}

AuthTokens Model

class AuthTokens {
  final String accessToken;     // Access token
  final String refreshToken;    // Refresh token
  final int expiresIn;          // Expiration time (seconds)
  final DateTime expiresAt;     // Expiration timestamp
}

User Model

class User {
  final String id;              // User ID (UUID)
  final String email;           // Email
  final String firstName;       // First name
  final String lastName;        // Last name
}

Security Mechanisms

1. Secure Token Storage

  • Uses flutter_secure_storage for encrypted token storage
  • Tokens are never saved in plain text in regular storage
  • Sensitive data is automatically cleared when app is uninstalled

2. Token Expiration Handling

  • Access Token is automatically refreshed using Refresh Token after expiration
  • Requires re-login when Refresh Token is invalid
  • All API requests check token validity

3. Device Tracking

  • Records device information on each login (device ID, model, OS)
  • Backend can manage user sessions based on device information

4. HTTPS Support

  • Production environment enforces HTTPS
  • Development environment supports HTTP (local testing only)

Theme & UI

Material Design 3

The app follows Material Design 3 specifications:

  • Dynamic color scheme (based on seed color #6366F1)
  • Rounded cards (12px border radius)
  • Responsive layout
  • Dark mode support (follows system)

Responsive Design

  • Pull-to-refresh support
  • Loading state indicators
  • Error state display
  • Empty state prompts

Development & Debugging

Environment Configuration

Android Emulator

// lib/services/api_config.dart
static String _baseUrl = 'http://10.0.2.2:3000';

iOS Simulator

static String _baseUrl = 'http://localhost:3000';

Physical Device

static String _baseUrl = 'http://YOUR_COMPUTER_IP:3000';
// Or use production URL
static String _baseUrl = 'https://your-domain.com';

Common Commands

# Install dependencies
flutter pub get

# Run app
flutter run

# Build APK
flutter build apk --release

# Build App Bundle
flutter build appbundle --release

# Build iOS
flutter build ios --release

# Code analysis
flutter analyze

# Run tests
flutter test

Debugging Tips

  1. View Network Requests:

    • Android Studio: Use Network Profiler
    • Or add print() statements in code
  2. View Stored Data:

    // Add at debugging point
    final prefs = await SharedPreferences.getInstance();
    print('Backend URL: ${prefs.getString('backend_url')}');
    
  3. Clear Local Data:

    # Android
    adb shell pm clear com.example.sure_mobile
    
    # iOS Simulator
    # Long press app icon -> Delete app -> Reinstall
    

CI/CD

The project is configured with GitHub Actions automated builds:

Trigger Conditions

  • Push to main branch
  • Pull Request to main branch
  • Only triggers when Flutter-related files change

Build Process

  1. Code analysis (flutter analyze)
  2. Run tests (flutter test)
  3. Android Release build (APK + AAB)
  4. iOS Release build (unsigned)
  5. Upload build artifacts

Download Build Artifacts

Available on GitHub Actions page:

  • app-release-apk: Android APK file
  • app-release-aab: Android App Bundle (for Google Play)
  • ios-build-unsigned: iOS app bundle (requires signing for distribution)

Future Extensions

Planned Features

  • Transaction History: View and manage transaction history
  • Account Sync: Support automatic bank account synchronization
  • Budget Management: Set and track budgets
  • Investment Tracking: View investment returns
  • AI Assistant: Financial advice and analysis
  • Push Notifications: Transaction alerts and account change notifications
  • Biometric Authentication: Fingerprint/Face ID quick login
  • Multi-language Support: Chinese/English interface switching
  • Chart Analysis: Financial data visualization

Technical Improvements

  • Offline mode support
  • Data caching optimization
  • More robust error handling
  • Unit tests and integration tests
  • Performance optimization