2221 lines
55 KiB
Markdown
2221 lines
55 KiB
Markdown
# Flutter Mobile App - Worker (EuroTile & Vasta Stone) Expert Guidelines
|
|
|
|
## 🎯 App Overview
|
|
A Flutter-based mobile application designed for contractors, distributors, architects, and brokers in the tile, interior, and construction industry. The app combines a powerful loyalty program with convenient ordering tools and project management capabilities.
|
|
|
|
### 🎯 Main Objectives:
|
|
- Powerful loyalty platform with rewards and membership tiers (Diamond/Platinum/Gold)
|
|
- Quick and efficient product ordering system
|
|
- Project, contract, and quote management
|
|
- Integration with promotions and special offers
|
|
- Real-time chat support
|
|
|
|
### 👥 Target Users:
|
|
- **Contractors (Thầu thợ)**: Construction project managers
|
|
- **Architects (Kiến trúc sư)**: Design professionals
|
|
- **Distributors (Đại lý phân phối)**: Product resellers
|
|
- **Real estate brokers (Môi giới)**: Real estate and construction brokers
|
|
|
|
### 📁 Reference Materials:
|
|
The `html/` folder contains UI/UX reference mockups that show the desired design and flow. These HTML files serve as design specifications for the Flutter implementation.
|
|
|
|
---
|
|
|
|
## 🤖 SUBAGENT DELEGATION SYSTEM 🤖
|
|
**CRITICAL: BE PROACTIVE WITH SUBAGENTS! YOU HAVE SPECIALIZED EXPERTS AVAILABLE!**
|
|
|
|
### 🚨 DELEGATION MINDSET
|
|
**Instead of thinking "I'll handle this myself"** **Think: "Which specialist is BEST suited for this task?"**
|
|
|
|
### 📋 AVAILABLE SPECIALISTS
|
|
You have access to these expert subagents - USE THEM PROACTIVELY:
|
|
|
|
#### 🎨 **flutter-widget-expert**
|
|
- **MUST BE USED for**: Loyalty cards, member cards, product grids, order cards, project cards, chat UI, form layouts, bottom navigation
|
|
- **Triggers**: "create widget", "build UI", "card design", "layout", "animation", "custom widget", "loyalty card", "member card"
|
|
|
|
#### 📊 **riverpod-expert**
|
|
- **MUST BE USED for**: Cart state, authentication state, loyalty points state, product state, order state, project state
|
|
- **Triggers**: "state management", "provider", "riverpod", "async state", "data flow", "cart", "auth state"
|
|
|
|
#### 🗄️ **hive-expert**
|
|
- **MUST BE USED for**: Local database, offline storage, cart persistence, user data caching, loyalty points cache
|
|
- **Triggers**: "database", "cache", "hive", "local storage", "persistence", "offline", "box"
|
|
|
|
#### 🌐 **api-integration-expert**
|
|
- **MUST BE USED for**: Authentication API, OTP verification, product API, loyalty API, order API, project API
|
|
- **Triggers**: "API", "HTTP", "dio", "REST", "backend", "authentication", "OTP", "sync"
|
|
|
|
#### 🏗️ **architecture-expert**
|
|
- **MUST BE USED for**: Feature organization, clean architecture setup, dependency injection, navigation structure
|
|
- **Triggers**: "architecture", "structure", "organization", "clean code", "refactor", "navigation"
|
|
|
|
#### ⚡ **performance-expert**
|
|
- **MUST BE USED for**: Image caching, list optimization, memory management, app performance
|
|
- **Triggers**: "performance", "optimization", "memory", "image cache", "slow", "lag", "scroll"
|
|
|
|
### 🎯 DELEGATION STRATEGY
|
|
**BEFORE starting ANY task, ASK YOURSELF:**
|
|
1. "Which of my specialists could handle this better?"
|
|
2. "Should I break this into parts for different specialists?"
|
|
3. "Would a specialist complete this faster and better?"
|
|
|
|
### 💼 WORK BALANCE RECOMMENDATION:
|
|
- **Simple Tasks (20%)**: Handle independently - quick fixes, minor updates
|
|
- **Complex Tasks (80%)**: Delegate to specialists for expert-level results
|
|
|
|
### 🔧 HOW TO DELEGATE
|
|
```
|
|
# Explicit delegation examples:
|
|
> Use the flutter-widget-expert to create the Diamond/Platinum/Gold loyalty card widget
|
|
> Have the riverpod-expert design the authentication and user session state management
|
|
> Ask the hive-expert to create the local database schema for products and orders
|
|
> Use the api-integration-expert to implement OTP authentication flow
|
|
> Have the architecture-expert organize the loyalty program feature structure
|
|
> Ask the performance-expert to optimize the product grid scrolling
|
|
```
|
|
|
|
---
|
|
|
|
## Flutter Best Practices
|
|
- Use Flutter 3.x features and Material 3 design
|
|
- Implement clean architecture with Riverpod for state management
|
|
- Use Hive for local database and offline functionality
|
|
- Follow proper dependency injection with Riverpod DI
|
|
- Implement proper error handling and user feedback
|
|
- Follow iOS and Android platform-specific design guidelines
|
|
- Support Vietnamese language (primary) and English (secondary)
|
|
- Mobile-first design optimized for phone screens
|
|
|
|
---
|
|
|
|
## Worker App Project Structure
|
|
|
|
```
|
|
lib/
|
|
core/
|
|
constants/
|
|
api_constants.dart # API endpoints, timeouts
|
|
app_constants.dart # App config, defaults, loyalty tiers
|
|
ui_constants.dart # Spacing, sizes, colors
|
|
storage_constants.dart # Hive box names, keys
|
|
theme/
|
|
app_theme.dart # Material 3 theme (primary blue #005B9A)
|
|
colors.dart # Brand color schemes
|
|
typography.dart # Roboto text styles
|
|
network/
|
|
dio_client.dart # HTTP client setup
|
|
api_interceptor.dart # Auth token, logging interceptors
|
|
network_info.dart # Connectivity status
|
|
errors/
|
|
exceptions.dart # Custom exceptions
|
|
failures.dart # Failure classes
|
|
utils/
|
|
formatters.dart # Currency, date, phone formatters
|
|
validators.dart # Form validation (Vietnamese phone, email)
|
|
extensions.dart # Dart extensions
|
|
qr_generator.dart # QR code generation for member cards
|
|
widgets/
|
|
custom_button.dart # Primary, secondary buttons
|
|
loading_indicator.dart # Loading states
|
|
error_widget.dart # Error displays
|
|
empty_state.dart # Empty list UI
|
|
bottom_nav_bar.dart # Main bottom navigation
|
|
floating_chat_button.dart # FAB for chat
|
|
|
|
features/
|
|
auth/
|
|
data/
|
|
datasources/
|
|
auth_remote_datasource.dart # Login, OTP, register APIs
|
|
auth_local_datasource.dart # Token storage
|
|
models/
|
|
user_model.dart # User with tier info
|
|
otp_response_model.dart
|
|
repositories/
|
|
auth_repository_impl.dart
|
|
domain/
|
|
entities/
|
|
user.dart # id, name, phone, email, tier, points
|
|
repositories/
|
|
auth_repository.dart
|
|
usecases/
|
|
login_with_phone.dart
|
|
verify_otp.dart
|
|
register_user.dart
|
|
logout.dart
|
|
get_current_user.dart
|
|
presentation/
|
|
providers/
|
|
auth_provider.dart
|
|
otp_timer_provider.dart
|
|
pages/
|
|
login_page.dart # Phone input
|
|
otp_verification_page.dart # 6-digit OTP
|
|
register_page.dart # Full registration form
|
|
widgets/
|
|
phone_input_field.dart
|
|
otp_input_field.dart # Auto-focus 6 digits
|
|
user_type_selector.dart # Contractor/Architect/etc
|
|
|
|
home/
|
|
data/
|
|
datasources/
|
|
member_card_local_datasource.dart
|
|
models/
|
|
member_card_model.dart
|
|
presentation/
|
|
providers/
|
|
member_card_provider.dart
|
|
pages:
|
|
home_page.dart # Main dashboard
|
|
widgets:
|
|
diamond_member_card.dart # Gradient card with QR
|
|
platinum_member_card.dart
|
|
gold_member_card.dart
|
|
quick_action_grid.dart
|
|
|
|
loyalty/
|
|
data/
|
|
datasources:
|
|
loyalty_remote_datasource.dart
|
|
loyalty_local_datasource.dart
|
|
models:
|
|
loyalty_points_model.dart
|
|
loyalty_transaction_model.dart
|
|
reward_model.dart
|
|
gift_model.dart
|
|
referral_model.dart
|
|
repositories:
|
|
loyalty_repository_impl.dart
|
|
domain:
|
|
entities:
|
|
loyalty_points.dart # currentPoints, tier, nextTierPoints
|
|
loyalty_transaction.dart # id, type, amount, description, date
|
|
reward.dart # id, title, pointsCost, image, expiry
|
|
gift.dart # id, code, status, validFrom, validTo
|
|
referral.dart # code, link, totalReferrals, pointsEarned
|
|
repositories:
|
|
loyalty_repository.dart
|
|
usecases:
|
|
get_loyalty_points.dart
|
|
get_points_history.dart
|
|
redeem_reward.dart
|
|
get_available_rewards.dart
|
|
get_my_gifts.dart
|
|
get_referral_info.dart
|
|
share_referral.dart
|
|
presentation:
|
|
providers:
|
|
loyalty_points_provider.dart
|
|
points_history_provider.dart
|
|
rewards_provider.dart
|
|
gifts_provider.dart
|
|
referral_provider.dart
|
|
pages:
|
|
loyalty_page.dart # Progress bar, tier info
|
|
rewards_page.dart # Grid of redeemable rewards
|
|
points_history_page.dart # Transaction list
|
|
referral_page.dart # Referral link & code
|
|
my_gifts_page.dart # Tabs: Active/Used/Expired
|
|
widgets:
|
|
tier_progress_bar.dart
|
|
points_badge.dart
|
|
reward_card.dart
|
|
gift_card.dart
|
|
referral_share_sheet.dart
|
|
|
|
products/
|
|
data:
|
|
datasources:
|
|
product_remote_datasource.dart
|
|
product_local_datasource.dart
|
|
models:
|
|
product_model.dart # Tile/construction products
|
|
category_model.dart
|
|
repositories:
|
|
product_repository_impl.dart
|
|
domain:
|
|
entities:
|
|
product.dart # id, name, sku, price, images, category
|
|
category.dart
|
|
repositories:
|
|
product_repository.dart
|
|
usecases:
|
|
get_all_products.dart
|
|
search_products.dart
|
|
get_products_by_category.dart
|
|
get_product_details.dart
|
|
presentation:
|
|
providers:
|
|
products_provider.dart
|
|
product_search_provider.dart
|
|
categories_provider.dart
|
|
pages:
|
|
products_page.dart # Grid with search & filters
|
|
product_detail_page.dart
|
|
widgets:
|
|
product_grid.dart
|
|
product_card.dart
|
|
product_search_bar.dart
|
|
category_filter_chips.dart
|
|
|
|
cart/
|
|
data:
|
|
datasources:
|
|
cart_local_datasource.dart # Hive persistence
|
|
models:
|
|
cart_item_model.dart
|
|
repositories:
|
|
cart_repository_impl.dart
|
|
domain:
|
|
entities:
|
|
cart_item.dart # productId, quantity, price
|
|
repositories:
|
|
cart_repository.dart
|
|
usecases:
|
|
add_to_cart.dart
|
|
remove_from_cart.dart
|
|
update_quantity.dart
|
|
clear_cart.dart
|
|
get_cart_items.dart
|
|
calculate_cart_total.dart
|
|
presentation:
|
|
providers:
|
|
cart_provider.dart
|
|
cart_total_provider.dart
|
|
pages:
|
|
cart_page.dart
|
|
checkout_page.dart
|
|
order_success_page.dart
|
|
widgets:
|
|
cart_item_card.dart
|
|
cart_summary.dart
|
|
quantity_selector.dart
|
|
payment_method_selector.dart
|
|
|
|
orders/
|
|
data:
|
|
datasources:
|
|
order_remote_datasource.dart
|
|
order_local_datasource.dart
|
|
models:
|
|
order_model.dart
|
|
order_item_model.dart
|
|
payment_model.dart
|
|
repositories:
|
|
order_repository_impl.dart
|
|
domain:
|
|
entities:
|
|
order.dart # orderNumber, items, total, status
|
|
order_item.dart
|
|
payment.dart
|
|
repositories:
|
|
order_repository.dart
|
|
usecases:
|
|
create_order.dart
|
|
get_orders.dart
|
|
get_order_details.dart
|
|
get_payments.dart
|
|
presentation:
|
|
providers:
|
|
orders_provider.dart
|
|
order_filter_provider.dart
|
|
payments_provider.dart
|
|
pages:
|
|
orders_page.dart # Tabs by status
|
|
order_detail_page.dart
|
|
payments_page.dart
|
|
widgets:
|
|
order_card.dart
|
|
order_status_badge.dart
|
|
order_timeline.dart
|
|
payment_card.dart
|
|
|
|
projects/
|
|
data:
|
|
datasources:
|
|
project_remote_datasource.dart
|
|
project_local_datasource.dart
|
|
models:
|
|
project_model.dart
|
|
quote_model.dart
|
|
repositories:
|
|
project_repository_impl.dart
|
|
domain:
|
|
entities:
|
|
project.dart # name, client, location, progress, status
|
|
quote.dart # number, client, amount, validity, status
|
|
repositories:
|
|
project_repository.dart
|
|
usecases:
|
|
create_project.dart
|
|
get_projects.dart
|
|
update_project_progress.dart
|
|
create_quote.dart
|
|
get_quotes.dart
|
|
presentation:
|
|
providers:
|
|
projects_provider.dart
|
|
project_form_provider.dart
|
|
quotes_provider.dart
|
|
pages:
|
|
projects_page.dart # List with progress bars
|
|
project_create_page.dart # Form
|
|
project_detail_page.dart
|
|
quotes_page.dart
|
|
quote_create_page.dart
|
|
widgets:
|
|
project_card.dart
|
|
project_progress_bar.dart
|
|
quote_card.dart
|
|
project_form.dart
|
|
|
|
chat/
|
|
data:
|
|
datasources:
|
|
chat_remote_datasource.dart # WebSocket/REST
|
|
chat_local_datasource.dart
|
|
models:
|
|
message_model.dart
|
|
chat_room_model.dart
|
|
repositories:
|
|
chat_repository_impl.dart
|
|
domain:
|
|
entities:
|
|
message.dart # id, text, senderId, timestamp, isRead
|
|
chat_room.dart
|
|
repositories:
|
|
chat_repository.dart
|
|
usecases:
|
|
send_message.dart
|
|
get_messages.dart
|
|
mark_as_read.dart
|
|
presentation:
|
|
providers:
|
|
chat_provider.dart
|
|
messages_provider.dart
|
|
typing_indicator_provider.dart
|
|
pages:
|
|
chat_page.dart
|
|
widgets:
|
|
message_bubble.dart
|
|
message_input.dart
|
|
typing_indicator.dart
|
|
chat_app_bar.dart
|
|
|
|
account/
|
|
data:
|
|
datasources:
|
|
profile_remote_datasource.dart
|
|
profile_local_datasource.dart
|
|
address_datasource.dart
|
|
models:
|
|
profile_model.dart
|
|
address_model.dart
|
|
repositories:
|
|
profile_repository_impl.dart
|
|
address_repository_impl.dart
|
|
domain:
|
|
entities:
|
|
profile.dart # Extended user info
|
|
address.dart # Delivery addresses
|
|
repositories:
|
|
profile_repository.dart
|
|
address_repository.dart
|
|
usecases:
|
|
get_profile.dart
|
|
update_profile.dart
|
|
upload_avatar.dart
|
|
change_password.dart
|
|
get_addresses.dart
|
|
add_address.dart
|
|
update_address.dart
|
|
delete_address.dart
|
|
presentation:
|
|
providers:
|
|
profile_provider.dart
|
|
avatar_provider.dart
|
|
addresses_provider.dart
|
|
pages:
|
|
account_page.dart # Menu
|
|
profile_edit_page.dart
|
|
addresses_page.dart
|
|
address_form_page.dart
|
|
password_change_page.dart
|
|
widgets:
|
|
profile_header.dart
|
|
account_menu_item.dart
|
|
address_card.dart
|
|
avatar_picker.dart
|
|
|
|
promotions/
|
|
data:
|
|
datasources:
|
|
promotion_remote_datasource.dart
|
|
models:
|
|
promotion_model.dart
|
|
repositories:
|
|
promotion_repository_impl.dart
|
|
domain:
|
|
entities:
|
|
promotion.dart # title, description, discount, validity
|
|
repositories:
|
|
promotion_repository.dart
|
|
usecases:
|
|
get_active_promotions.dart
|
|
presentation:
|
|
providers:
|
|
promotions_provider.dart
|
|
pages:
|
|
promotions_page.dart
|
|
widgets:
|
|
promotion_card.dart
|
|
promotion_banner.dart
|
|
|
|
notifications/
|
|
data:
|
|
datasources:
|
|
notification_remote_datasource.dart
|
|
notification_local_datasource.dart
|
|
models:
|
|
notification_model.dart
|
|
repositories:
|
|
notification_repository_impl.dart
|
|
domain:
|
|
entities:
|
|
notification.dart # title, body, type, isRead, timestamp
|
|
repositories:
|
|
notification_repository.dart
|
|
usecases:
|
|
get_notifications.dart
|
|
mark_as_read.dart
|
|
clear_all.dart
|
|
presentation:
|
|
providers:
|
|
notifications_provider.dart
|
|
notification_badge_provider.dart
|
|
pages:
|
|
notifications_page.dart # Tabs: All/Orders/System/Promos
|
|
widgets:
|
|
notification_card.dart
|
|
notification_badge.dart
|
|
|
|
shared/
|
|
widgets/
|
|
custom_app_bar.dart
|
|
gradient_card.dart # For member cards
|
|
status_badge.dart
|
|
price_display.dart
|
|
vietnamese_phone_field.dart
|
|
date_picker_field.dart
|
|
|
|
main.dart
|
|
app.dart # Root widget with ProviderScope
|
|
|
|
test/
|
|
unit/
|
|
features/
|
|
auth/
|
|
loyalty/
|
|
products/
|
|
cart/
|
|
orders/
|
|
projects/
|
|
widget/
|
|
widgets/
|
|
integration/
|
|
```
|
|
|
|
---
|
|
|
|
# App Context - Worker Mobile App
|
|
|
|
## About This App
|
|
A comprehensive Flutter mobile application designed for workers in the tile and construction industry. The app enables contractors, architects, distributors, and brokers to manage their loyalty rewards, browse products, create orders, manage construction projects, and track business performance—all through an intuitive mobile interface optimized for speed and efficiency.
|
|
|
|
## Design References
|
|
The `html/` folder contains 25+ HTML mockup files that serve as UI/UX design references:
|
|
- Visual design and layout specifications
|
|
- Color schemes and branding
|
|
- User flow and navigation patterns
|
|
- Component designs and interactions
|
|
- Form layouts and validation patterns
|
|
|
|
**These HTML files should be referenced when building Flutter widgets to match the desired design.**
|
|
|
|
---
|
|
|
|
## Core Features
|
|
|
|
### 🔐 Authentication System
|
|
|
|
#### Login Flow
|
|
**Pages**: `login_page.dart`, `otp_verification_page.dart`, `register_page.dart`
|
|
|
|
**Features**:
|
|
- Phone number-based authentication (Vietnamese format)
|
|
- 6-digit OTP verification with auto-focus
|
|
- Auto-advance between OTP input fields
|
|
- Resend OTP with 60-second cooldown timer
|
|
- Full registration form with user type selection
|
|
- Form validation for all fields
|
|
|
|
**State Management**:
|
|
```dart
|
|
final authProvider = AsyncNotifierProvider<AuthNotifier, AuthState>
|
|
final otpTimerProvider = StateNotifierProvider<OTPTimerNotifier, int>
|
|
```
|
|
|
|
**Key Widgets**:
|
|
- `PhoneInputField`: Vietnamese phone number format (+84)
|
|
- `OTPInputField`: 6-digit auto-focus input
|
|
- `UserTypeSelector`: Radio buttons for Contractor/Architect/Distributor/Broker
|
|
|
|
**Design Reference**: `html/login.html`, `html/otp.html`, `html/register.html`
|
|
|
|
---
|
|
|
|
### 🏠 Home Screen with Membership Cards
|
|
|
|
**Purpose**: Dashboard showing member tier card and quick access to features
|
|
|
|
**Pages**: `home_page.dart`
|
|
|
|
**Key Features**:
|
|
- **Membership Card Display**:
|
|
- Three tier widgets: `DiamondMemberCard`, `PlatinumMemberCard`, `GoldMemberCard`
|
|
- Gradient backgrounds (blue-purple for Diamond, grey-silver for Platinum, yellow-gold for Gold)
|
|
- Member name, ID, and points balance
|
|
- QR code generation for member identification
|
|
- Animated card transitions
|
|
|
|
- **Quick Action Grid**:
|
|
- 4-6 icon buttons for main features
|
|
- Products, Loyalty, Orders, Projects, Promotions
|
|
|
|
- **Bottom Navigation**:
|
|
- 5 tabs: Home, Products, Loyalty, Account, More
|
|
- Active state indicators
|
|
- Badge for notifications
|
|
|
|
- **Floating Action Button**:
|
|
- Chat support access
|
|
- Positioned bottom-right
|
|
- Accent cyan color (#35C6F4)
|
|
|
|
**State Management**:
|
|
```dart
|
|
final memberCardProvider = Provider<MemberCard>((ref) {
|
|
final user = ref.watch(authProvider).user;
|
|
return MemberCard(
|
|
tier: user.memberTier,
|
|
name: user.name,
|
|
memberId: user.id,
|
|
points: user.points,
|
|
qrCode: generateQRCode(user.id),
|
|
);
|
|
});
|
|
```
|
|
|
|
**Design Reference**: `html/index.html`
|
|
|
|
---
|
|
|
|
### 🎁 Loyalty Program System
|
|
|
|
#### Main Loyalty Page
|
|
**Page**: `loyalty_page.dart`
|
|
|
|
**Features**:
|
|
- **Tier Progress Display**:
|
|
- Current points and tier
|
|
- Progress bar to next tier (%)
|
|
- Points needed for next tier
|
|
- Animated progress indicator
|
|
|
|
- **Tier Benefits Cards**:
|
|
- Points multiplier info
|
|
- Special offers access
|
|
- Exclusive discounts
|
|
- Priority support
|
|
|
|
- **Quick Actions**:
|
|
- Navigate to rewards, history, referral, gifts
|
|
|
|
**Widgets**:
|
|
- `TierProgressBar`: Custom painted progress with gradient
|
|
- `PointsBadge`: Circular points display
|
|
- `TierBenefitsCard`: Expandable card with benefits list
|
|
|
|
**State Management**:
|
|
```dart
|
|
final loyaltyPointsProvider = AsyncNotifierProvider<LoyaltyPointsNotifier, LoyaltyPoints>
|
|
```
|
|
|
|
**Design Reference**: `html/loyalty.html`
|
|
|
|
---
|
|
|
|
#### Rewards Page
|
|
**Page**: `rewards_page.dart`
|
|
|
|
**Features**:
|
|
- Grid of available rewards to redeem
|
|
- Filter by category (Vouchers/Products/Services)
|
|
- Reward cards showing:
|
|
- Image
|
|
- Title and description
|
|
- Points cost
|
|
- Expiration date
|
|
- Redeem button (disabled if insufficient points)
|
|
- Redemption confirmation dialog
|
|
- Success animation and gift code display
|
|
|
|
**Widgets**:
|
|
- `RewardCard`: Card with image, info, and redeem button
|
|
- `RewardRedemptionDialog`: Confirmation dialog
|
|
- `GiftCodeDisplay`: Copy-able gift code after redemption
|
|
|
|
**Design Reference**: `html/loyalty-rewards.html`
|
|
|
|
---
|
|
|
|
#### Points History Page
|
|
**Page**: `points_history_page.dart`
|
|
|
|
**Features**:
|
|
- List of all points transactions
|
|
- Transaction cards showing:
|
|
- Type (earned/spent) with icon and color
|
|
- Points amount (+/- display)
|
|
- Description
|
|
- Date and time
|
|
- New balance after transaction
|
|
- Filter by date range (Today/Week/Month/All)
|
|
- Dispute button for each transaction
|
|
- Pull-to-refresh
|
|
|
|
**Widgets**:
|
|
- `PointsTransactionCard`: Card with transaction details
|
|
- `DateRangeFilter`: Chip filter row
|
|
- `DisputeButton`: Opens support dialog
|
|
|
|
**Design Reference**: `html/points-history.html`
|
|
|
|
---
|
|
|
|
#### Referral Program Page
|
|
**Page**: `referral_page.dart`
|
|
|
|
**Features**:
|
|
- User avatar and tier display
|
|
- Total referrals count
|
|
- Total points earned from referrals
|
|
- Referral code (large, copy-able)
|
|
- Referral link (copy and share buttons)
|
|
- Share sheet integration (WhatsApp, Telegram, etc.)
|
|
- Benefits explanation cards
|
|
- How it works guide
|
|
|
|
**Widgets**:
|
|
- `ReferralInfoCard`: Stats display
|
|
- `ReferralCodeDisplay`: Large code with copy button
|
|
- `ReferralLinkShare`: Link with copy/share buttons
|
|
- `ReferralShareSheet`: Bottom sheet with share options
|
|
|
|
**State Management**:
|
|
```dart
|
|
final referralProvider = AsyncNotifierProvider<ReferralNotifier, Referral>
|
|
```
|
|
|
|
**Design Reference**: `html/referral.html`
|
|
|
|
---
|
|
|
|
#### My Gifts Page
|
|
**Page**: `my_gifts_page.dart`
|
|
|
|
**Features**:
|
|
- Tabs: Active (valid), Used, Expired
|
|
- Gift cards showing:
|
|
- Gift image and title
|
|
- Gift code (copy-able)
|
|
- Valid from/to dates
|
|
- Usage instructions
|
|
- Status badge
|
|
- Use button (for active gifts)
|
|
- Empty state for each tab
|
|
- Filter and sort options
|
|
|
|
**Widgets**:
|
|
- `GiftCard`: Card with gift details and actions
|
|
- `GiftStatusBadge`: Color-coded status (green/grey/red)
|
|
- `GiftCodeCopy`: Code with copy button
|
|
|
|
**Design Reference**: `html/my-gifts.html`
|
|
|
|
---
|
|
|
|
### 🛍️ Products & Shopping
|
|
|
|
#### Products Page
|
|
**Page**: `products_page.dart`
|
|
|
|
**Features**:
|
|
- Search bar with real-time filtering
|
|
- Category filter chips (horizontal scroll)
|
|
- Product grid (2 columns)
|
|
- Product cards showing:
|
|
- Image with cached_network_image
|
|
- Product name and SKU
|
|
- Price (with sale price if applicable)
|
|
- Stock indicator
|
|
- Add to cart button
|
|
- Pull-to-refresh
|
|
- Infinite scroll pagination
|
|
- Empty state
|
|
|
|
**Widgets**:
|
|
- `ProductGrid`: GridView.builder with pagination
|
|
- `ProductCard`: Card with image, info, and actions
|
|
- `ProductSearchBar`: Search with clear button
|
|
- `CategoryFilterChips`: Horizontal chip list
|
|
|
|
**State Management**:
|
|
```dart
|
|
final productsProvider = AsyncNotifierProvider<ProductsNotifier, List<Product>>
|
|
final productSearchProvider = StateProvider<String>
|
|
final selectedCategoryProvider = StateProvider<String?>
|
|
```
|
|
|
|
**Design Reference**: `html/products.html`
|
|
|
|
---
|
|
|
|
#### Cart & Checkout
|
|
**Pages**: `cart_page.dart`, `checkout_page.dart`, `order_success_page.dart`
|
|
|
|
**Cart Features**:
|
|
- List of cart items
|
|
- Item cards with:
|
|
- Product image and name
|
|
- Price per unit
|
|
- Quantity selector (+/-)
|
|
- Remove button
|
|
- Line total
|
|
- Cart summary:
|
|
- Subtotal
|
|
- Discount (if applied)
|
|
- Shipping
|
|
- Total
|
|
- Clear cart button
|
|
- Checkout button
|
|
|
|
**Checkout Features**:
|
|
- Delivery address selection/entry
|
|
- Payment method selection (COD/Bank Transfer/Card/E-wallet)
|
|
- Order summary
|
|
- Place order button with loading state
|
|
- Form validation
|
|
|
|
**Success Features**:
|
|
- Success animation (Lottie)
|
|
- Order number display
|
|
- Estimated delivery date
|
|
- Order details summary
|
|
- Action buttons: View order, Continue shopping
|
|
|
|
**State Management**:
|
|
```dart
|
|
final cartProvider = NotifierProvider<CartNotifier, List<CartItem>>
|
|
final cartTotalProvider = Provider<double>
|
|
```
|
|
|
|
**Design Reference**: `html/cart.html`, `html/checkout.html`, `html/order-success.html`
|
|
|
|
---
|
|
|
|
### 📦 Orders Management
|
|
|
|
**Pages**: `orders_page.dart`, `order_detail_page.dart`, `payments_page.dart`
|
|
|
|
**Orders Features**:
|
|
- Tab bar for status filtering:
|
|
- All, Pending, Processing, Shipping, Completed, Cancelled
|
|
- Order cards showing:
|
|
- Order number and date
|
|
- Customer info (for distributors/brokers)
|
|
- Product list (collapsed)
|
|
- Total amount
|
|
- Status badge (color-coded)
|
|
- Action buttons (View details, Track)
|
|
- Search by order number
|
|
- Filter by date range
|
|
- Pull-to-refresh
|
|
|
|
**Order Detail Features**:
|
|
- Full order information
|
|
- Timeline showing order status progression
|
|
- Product list with images
|
|
- Delivery address
|
|
- Payment info
|
|
- Action buttons (Contact support, Reorder)
|
|
|
|
**Payments Features**:
|
|
- List of payment transactions
|
|
- Payment cards showing:
|
|
- Payment ID
|
|
- Order reference
|
|
- Amount
|
|
- Payment method
|
|
- Date
|
|
- Status (Processing/Completed)
|
|
- Search and filter options
|
|
|
|
**State Management**:
|
|
```dart
|
|
final ordersProvider = AsyncNotifierProvider<OrdersNotifier, List<Order>>
|
|
final orderFilterProvider = StateProvider<OrderStatus?>
|
|
final paymentsProvider = AsyncNotifierProvider<PaymentsNotifier, List<Payment>>
|
|
```
|
|
|
|
**Design Reference**: `html/orders.html`, `html/payments.html`
|
|
|
|
---
|
|
|
|
### 🏗️ Projects & Quotes Management
|
|
|
|
#### Projects
|
|
**Pages**: `projects_page.dart`, `project_create_page.dart`, `project_detail_page.dart`
|
|
|
|
**Projects Features**:
|
|
- List of construction projects
|
|
- Project cards showing:
|
|
- Project name and code
|
|
- Client/owner info
|
|
- Location
|
|
- Start and end dates
|
|
- Progress bar (%)
|
|
- Status badge (Planning/In Progress/Completed)
|
|
- Budget/value
|
|
- Create new project FAB
|
|
- Filter by status, date, client
|
|
- Search projects
|
|
|
|
**Project Create Features**:
|
|
- Form with fields:
|
|
- Project name and code
|
|
- Client name and phone
|
|
- Location/address
|
|
- Start/end dates
|
|
- Project type (Residential/Commercial/Industrial)
|
|
- Budget
|
|
- Description and notes
|
|
- Form validation
|
|
- Save draft functionality
|
|
- Date pickers
|
|
- Auto-generate project code option
|
|
|
|
**State Management**:
|
|
```dart
|
|
final projectsProvider = AsyncNotifierProvider<ProjectsNotifier, List<Project>>
|
|
final projectFormProvider = StateNotifierProvider<ProjectFormNotifier, ProjectFormState>
|
|
```
|
|
|
|
**Design Reference**: `html/projects.html`, `html/project-create.html`
|
|
|
|
---
|
|
|
|
#### Quotes
|
|
**Pages**: `quotes_page.dart`, `quote_create_page.dart`
|
|
|
|
**Quotes Features**:
|
|
- List of quotations
|
|
- Quote cards showing:
|
|
- Quote number and date
|
|
- Client info
|
|
- Total amount
|
|
- Validity period
|
|
- Status badge (Draft/Sent/Accepted/Rejected/Expired)
|
|
- Actions:
|
|
- Create new quote
|
|
- Edit draft
|
|
- Send to client
|
|
- Convert to order
|
|
- Duplicate
|
|
- Search and filter
|
|
|
|
**Design Reference**: `html/quotes.html`
|
|
|
|
---
|
|
|
|
### 💬 Chat Support
|
|
|
|
**Pages**: `chat_page.dart`
|
|
|
|
**Features**:
|
|
- Real-time chat interface
|
|
- Message bubbles (sent/received)
|
|
- Timestamp for messages
|
|
- User avatars
|
|
- Typing indicator animation
|
|
- Message input with send button
|
|
- File attachment button
|
|
- Auto-scroll to latest message
|
|
- Read receipts
|
|
- Support agent info header
|
|
|
|
**Widgets**:
|
|
- `MessageBubble`: Custom bubble with tail
|
|
- `MessageInput`: Text field with send button
|
|
- `TypingIndicator`: Animated dots
|
|
- `ChatAppBar`: Custom app bar with agent info
|
|
|
|
**State Management**:
|
|
```dart
|
|
final chatProvider = AsyncNotifierProvider<ChatNotifier, ChatRoom>
|
|
final messagesProvider = StreamProvider<List<Message>>
|
|
final typingIndicatorProvider = StateProvider<bool>
|
|
```
|
|
|
|
**Design Reference**: `html/chat.html`
|
|
|
|
---
|
|
|
|
### 👤 Account Management
|
|
|
|
#### Account Page
|
|
**Page**: `account_page.dart`
|
|
|
|
**Features**:
|
|
- Profile header with avatar, name, tier, points
|
|
- Menu sections:
|
|
- Personal: Profile, Password, Addresses
|
|
- Business: Company info, Tax info
|
|
- Settings: Notifications, Language, Theme
|
|
- Support: Help, Contact, Terms, Privacy
|
|
- App: Version, Logout
|
|
- Navigation to sub-pages
|
|
- Logout confirmation dialog
|
|
|
|
**Design Reference**: `html/account.html`
|
|
|
|
---
|
|
|
|
#### Profile Edit
|
|
**Page**: `profile_edit_page.dart`
|
|
|
|
**Features**:
|
|
- Avatar upload with image picker
|
|
- Image cropping
|
|
- Form fields:
|
|
- Full name
|
|
- Email
|
|
- Phone (read-only)
|
|
- Date of birth
|
|
- Gender
|
|
- Company name
|
|
- Tax ID
|
|
- Business address
|
|
- User type
|
|
- Form validation
|
|
- Unsaved changes warning
|
|
- Save/cancel buttons
|
|
|
|
**Widgets**:
|
|
- `AvatarPicker`: Image picker with crop
|
|
- `DatePickerField`: Date selection
|
|
- `GenderSelector`: Radio buttons
|
|
|
|
**Design Reference**: `html/profile-edit.html`
|
|
|
|
---
|
|
|
|
#### Addresses Management
|
|
**Pages**: `addresses_page.dart`, `address_form_page.dart`
|
|
|
|
**Features**:
|
|
- List of saved addresses
|
|
- Address cards with:
|
|
- Label (Home/Office/Other)
|
|
- Recipient name and phone
|
|
- Full address
|
|
- Default badge
|
|
- Edit/delete actions
|
|
- Add new address FAB
|
|
- Set as default toggle
|
|
- Form with:
|
|
- Recipient name
|
|
- Phone number
|
|
- Address fields (Street, City, District, Ward, Postal code)
|
|
- Address type
|
|
- Set as default checkbox
|
|
- Form validation
|
|
|
|
**Design Reference**: `html/addresses.html`
|
|
|
|
---
|
|
|
|
#### Change Password
|
|
**Page**: `password_change_page.dart`
|
|
|
|
**Features**:
|
|
- Form with:
|
|
- Current password
|
|
- New password
|
|
- Confirm new password
|
|
- Show/hide password toggles
|
|
- Password strength indicator
|
|
- Requirements checklist:
|
|
- Min 8 characters
|
|
- Uppercase letter
|
|
- Lowercase letter
|
|
- Number
|
|
- Special character
|
|
- Real-time validation
|
|
- Success message and redirect
|
|
|
|
**Widgets**:
|
|
- `PasswordStrengthIndicator`: Visual strength meter
|
|
- `PasswordRequirements`: Checklist with check/cross icons
|
|
|
|
**Design Reference**: `html/password-change.html`
|
|
|
|
---
|
|
|
|
### 🎉 Promotions & Notifications
|
|
|
|
#### Promotions
|
|
**Page**: `promotions_page.dart`
|
|
|
|
**Features**:
|
|
- List of active promotions
|
|
- Promotion cards with:
|
|
- Banner image
|
|
- Title and description
|
|
- Discount percentage/amount
|
|
- Validity period
|
|
- Terms and conditions
|
|
- Apply/claim button
|
|
- Filter by category
|
|
- Status indicators (Active/Upcoming/Expired)
|
|
|
|
**Design Reference**: `html/promotions.html`
|
|
|
|
---
|
|
|
|
#### Notifications
|
|
**Page**: `notifications_page.dart`
|
|
|
|
**Features**:
|
|
- Tabs: All, Orders, System, Promotions
|
|
- Notification cards with:
|
|
- Icon/avatar
|
|
- Title and content
|
|
- Timestamp
|
|
- Read/unread indicator
|
|
- Action buttons
|
|
- Mark as read
|
|
- Clear all button
|
|
- Pull-to-refresh
|
|
- Badge count on bottom nav
|
|
|
|
**Design Reference**: `html/notifications.html`
|
|
|
|
---
|
|
|
|
## UI/UX Design System
|
|
|
|
### Color Palette
|
|
```dart
|
|
// colors.dart
|
|
class AppColors {
|
|
// Primary
|
|
static const primaryBlue = Color(0xFF005B9A);
|
|
static const lightBlue = Color(0xFF38B6FF);
|
|
static const accentCyan = Color(0xFF35C6F4);
|
|
|
|
// Status
|
|
static const success = Color(0xFF28a745);
|
|
static const warning = Color(0xFFffc107);
|
|
static const danger = Color(0xFFdc3545);
|
|
static const info = Color(0xFF17a2b8);
|
|
|
|
// Neutrals
|
|
static const grey50 = Color(0xFFf8f9fa);
|
|
static const grey100 = Color(0xFFe9ecef);
|
|
static const grey500 = Color(0xFF6c757d);
|
|
static const grey900 = Color(0xFF343a40);
|
|
|
|
// Tier Gradients
|
|
static const diamondGradient = LinearGradient(
|
|
colors: [Color(0xFF4A00E0), Color(0xFF8E2DE2)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
);
|
|
|
|
static const platinumGradient = LinearGradient(
|
|
colors: [Color(0xFF7F8C8D), Color(0xFFBDC3C7)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
);
|
|
|
|
static const goldGradient = LinearGradient(
|
|
colors: [Color(0xFFf7b733), Color(0xFFfc4a1a)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
);
|
|
}
|
|
```
|
|
|
|
### Typography
|
|
```dart
|
|
// typography.dart
|
|
class AppTypography {
|
|
static const fontFamily = 'Roboto';
|
|
|
|
static const displayLarge = TextStyle(
|
|
fontSize: 32,
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: fontFamily,
|
|
);
|
|
|
|
static const headlineLarge = TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.w600,
|
|
fontFamily: fontFamily,
|
|
);
|
|
|
|
static const titleLarge = TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w500,
|
|
fontFamily: fontFamily,
|
|
);
|
|
|
|
static const bodyLarge = TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.normal,
|
|
fontFamily: fontFamily,
|
|
);
|
|
|
|
static const labelSmall = TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.normal,
|
|
fontFamily: fontFamily,
|
|
);
|
|
}
|
|
```
|
|
|
|
### Component Specifications
|
|
|
|
#### Member Card Design
|
|
```dart
|
|
class MemberCardSpecs {
|
|
static const double width = double.infinity;
|
|
static const double height = 200;
|
|
static const double borderRadius = 16;
|
|
static const double elevation = 8;
|
|
static const EdgeInsets padding = EdgeInsets.all(20);
|
|
|
|
// QR Code
|
|
static const double qrSize = 80;
|
|
static const double qrBackgroundSize = 90;
|
|
|
|
// Points Display
|
|
static const double pointsFontSize = 28;
|
|
static const FontWeight pointsFontWeight = FontWeight.bold;
|
|
}
|
|
```
|
|
|
|
#### Status Badges
|
|
```dart
|
|
class StatusBadge extends StatelessWidget {
|
|
final String status;
|
|
final Color color;
|
|
|
|
static Color getColorForStatus(OrderStatus status) {
|
|
switch (status) {
|
|
case OrderStatus.pending:
|
|
return AppColors.info;
|
|
case OrderStatus.processing:
|
|
return AppColors.warning;
|
|
case OrderStatus.shipping:
|
|
return AppColors.lightBlue;
|
|
case OrderStatus.completed:
|
|
return AppColors.success;
|
|
case OrderStatus.cancelled:
|
|
return AppColors.danger;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Bottom Navigation
|
|
```dart
|
|
class BottomNavSpecs {
|
|
static const double height = 72;
|
|
static const double iconSize = 24;
|
|
static const double selectedIconSize = 28;
|
|
static const double labelFontSize = 12;
|
|
static const Color selectedColor = AppColors.primaryBlue;
|
|
static const Color unselectedColor = AppColors.grey500;
|
|
}
|
|
```
|
|
|
|
#### Floating Action Button
|
|
```dart
|
|
class FABSpecs {
|
|
static const double size = 56;
|
|
static const double elevation = 6;
|
|
static const Color backgroundColor = AppColors.accentCyan;
|
|
static const Color iconColor = Colors.white;
|
|
static const double iconSize = 24;
|
|
static const Offset position = Offset(16, 16); // from bottom-right
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Technical Architecture
|
|
|
|
### State Management (Riverpod 2.x)
|
|
|
|
#### Authentication State
|
|
```dart
|
|
@riverpod
|
|
class Auth extends _$Auth {
|
|
@override
|
|
Future<AuthState> build() async {
|
|
final token = await _getStoredToken();
|
|
if (token != null) {
|
|
final user = await _getUserFromToken(token);
|
|
return AuthState.authenticated(user);
|
|
}
|
|
return const AuthState.unauthenticated();
|
|
}
|
|
|
|
Future<void> loginWithPhone(String phone) async {
|
|
state = const AsyncValue.loading();
|
|
state = await AsyncValue.guard(() async {
|
|
await ref.read(authRepositoryProvider).requestOTP(phone);
|
|
return AuthState.otpSent(phone);
|
|
});
|
|
}
|
|
|
|
Future<void> verifyOTP(String phone, String otp) async {
|
|
state = const AsyncValue.loading();
|
|
state = await AsyncValue.guard(() async {
|
|
final response = await ref.read(authRepositoryProvider).verifyOTP(phone, otp);
|
|
await _storeToken(response.token);
|
|
return AuthState.authenticated(response.user);
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Cart State
|
|
```dart
|
|
@riverpod
|
|
class Cart extends _$Cart {
|
|
@override
|
|
List<CartItem> build() {
|
|
return ref.watch(cartRepositoryProvider).getCartItems();
|
|
}
|
|
|
|
void addItem(Product product, int quantity) {
|
|
state = [
|
|
...state,
|
|
CartItem(
|
|
id: product.id,
|
|
productName: product.name,
|
|
price: product.price,
|
|
quantity: quantity,
|
|
imageUrl: product.images.first,
|
|
),
|
|
];
|
|
_persistCart();
|
|
}
|
|
|
|
void updateQuantity(String itemId, int quantity) {
|
|
state = state.map((item) {
|
|
if (item.id == itemId) {
|
|
return item.copyWith(quantity: quantity);
|
|
}
|
|
return item;
|
|
}).toList();
|
|
_persistCart();
|
|
}
|
|
|
|
void removeItem(String itemId) {
|
|
state = state.where((item) => item.id != itemId).toList();
|
|
_persistCart();
|
|
}
|
|
|
|
void clear() {
|
|
state = [];
|
|
_persistCart();
|
|
}
|
|
|
|
void _persistCart() {
|
|
ref.read(cartRepositoryProvider).saveCart(state);
|
|
}
|
|
}
|
|
|
|
@riverpod
|
|
double cartTotal(CartTotalRef ref) {
|
|
final items = ref.watch(cartProvider);
|
|
return items.fold(0, (sum, item) => sum + (item.price * item.quantity));
|
|
}
|
|
```
|
|
|
|
#### Loyalty Points State
|
|
```dart
|
|
@riverpod
|
|
class LoyaltyPoints extends _$LoyaltyPoints {
|
|
@override
|
|
Future<LoyaltyPointsData> build() async {
|
|
return await ref.read(loyaltyRepositoryProvider).getLoyaltyPoints();
|
|
}
|
|
|
|
Future<void> refresh() async {
|
|
state = const AsyncValue.loading();
|
|
state = await AsyncValue.guard(() async {
|
|
return await ref.read(loyaltyRepositoryProvider).getLoyaltyPoints();
|
|
});
|
|
}
|
|
|
|
Future<void> redeemReward(Reward reward) async {
|
|
final result = await ref.read(loyaltyRepositoryProvider).redeemReward(reward.id);
|
|
if (result.success) {
|
|
await refresh();
|
|
// Show success dialog with gift code
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Database Schema (Hive)
|
|
|
|
#### User Model
|
|
```dart
|
|
@HiveType(typeId: 0)
|
|
class UserModel extends HiveObject {
|
|
@HiveField(0)
|
|
final String id;
|
|
|
|
@HiveField(1)
|
|
final String name;
|
|
|
|
@HiveField(2)
|
|
final String phone;
|
|
|
|
@HiveField(3)
|
|
final String email;
|
|
|
|
@HiveField(4)
|
|
final String? avatar;
|
|
|
|
@HiveField(5)
|
|
final MemberTier memberTier; // diamond, platinum, gold
|
|
|
|
@HiveField(6)
|
|
final int points;
|
|
|
|
@HiveField(7)
|
|
final String referralCode;
|
|
|
|
@HiveField(8)
|
|
final String? company;
|
|
|
|
@HiveField(9)
|
|
final UserType userType; // contractor, architect, distributor, broker
|
|
|
|
@HiveField(10)
|
|
final DateTime createdAt;
|
|
}
|
|
```
|
|
|
|
#### Product Model
|
|
```dart
|
|
@HiveType(typeId: 1)
|
|
class ProductModel extends HiveObject {
|
|
@HiveField(0)
|
|
final String id;
|
|
|
|
@HiveField(1)
|
|
final String name;
|
|
|
|
@HiveField(2)
|
|
final String sku;
|
|
|
|
@HiveField(3)
|
|
final String description;
|
|
|
|
@HiveField(4)
|
|
final double price;
|
|
|
|
@HiveField(5)
|
|
final double? salePrice;
|
|
|
|
@HiveField(6)
|
|
final List<String> images;
|
|
|
|
@HiveField(7)
|
|
final String categoryId;
|
|
|
|
@HiveField(8)
|
|
final int stock;
|
|
|
|
@HiveField(9)
|
|
final bool isAvailable;
|
|
|
|
@HiveField(10)
|
|
final DateTime createdAt;
|
|
}
|
|
```
|
|
|
|
#### Cart Item Model
|
|
```dart
|
|
@HiveType(typeId: 2)
|
|
class CartItemModel extends HiveObject {
|
|
@HiveField(0)
|
|
final String id;
|
|
|
|
@HiveField(1)
|
|
final String productId;
|
|
|
|
@HiveField(2)
|
|
final String productName;
|
|
|
|
@HiveField(3)
|
|
final double price;
|
|
|
|
@HiveField(4)
|
|
final int quantity;
|
|
|
|
@HiveField(5)
|
|
final String imageUrl;
|
|
|
|
@HiveField(6)
|
|
final DateTime addedAt;
|
|
}
|
|
```
|
|
|
|
#### Order Model
|
|
```dart
|
|
@HiveType(typeId: 3)
|
|
class OrderModel extends HiveObject {
|
|
@HiveField(0)
|
|
final String id;
|
|
|
|
@HiveField(1)
|
|
final String orderNumber;
|
|
|
|
@HiveField(2)
|
|
final String customerId;
|
|
|
|
@HiveField(3)
|
|
final String customerName;
|
|
|
|
@HiveField(4)
|
|
final List<OrderItemModel> items;
|
|
|
|
@HiveField(5)
|
|
final double subtotal;
|
|
|
|
@HiveField(6)
|
|
final double discount;
|
|
|
|
@HiveField(7)
|
|
final double shipping;
|
|
|
|
@HiveField(8)
|
|
final double total;
|
|
|
|
@HiveField(9)
|
|
final OrderStatus status;
|
|
|
|
@HiveField(10)
|
|
final String paymentMethod;
|
|
|
|
@HiveField(11)
|
|
final AddressModel deliveryAddress;
|
|
|
|
@HiveField(12)
|
|
final DateTime createdAt;
|
|
|
|
@HiveField(13)
|
|
final DateTime? completedAt;
|
|
}
|
|
```
|
|
|
|
#### Project Model
|
|
```dart
|
|
@HiveType(typeId: 4)
|
|
class ProjectModel extends HiveObject {
|
|
@HiveField(0)
|
|
final String id;
|
|
|
|
@HiveField(1)
|
|
final String name;
|
|
|
|
@HiveField(2)
|
|
final String code;
|
|
|
|
@HiveField(3)
|
|
final String client;
|
|
|
|
@HiveField(4)
|
|
final String clientPhone;
|
|
|
|
@HiveField(5)
|
|
final String location;
|
|
|
|
@HiveField(6)
|
|
final DateTime startDate;
|
|
|
|
@HiveField(7)
|
|
final DateTime endDate;
|
|
|
|
@HiveField(8)
|
|
final int progress; // 0-100
|
|
|
|
@HiveField(9)
|
|
final ProjectStatus status; // planning, inProgress, completed
|
|
|
|
@HiveField(10)
|
|
final double budget;
|
|
|
|
@HiveField(11)
|
|
final String description;
|
|
|
|
@HiveField(12)
|
|
final ProjectType type; // residential, commercial, industrial
|
|
|
|
@HiveField(13)
|
|
final DateTime createdAt;
|
|
}
|
|
```
|
|
|
|
#### Loyalty Transaction Model
|
|
```dart
|
|
@HiveType(typeId: 5)
|
|
class LoyaltyTransactionModel extends HiveObject {
|
|
@HiveField(0)
|
|
final String id;
|
|
|
|
@HiveField(1)
|
|
final TransactionType type; // earn, redeem
|
|
|
|
@HiveField(2)
|
|
final int amount;
|
|
|
|
@HiveField(3)
|
|
final String description;
|
|
|
|
@HiveField(4)
|
|
final DateTime timestamp;
|
|
|
|
@HiveField(5)
|
|
final int newBalance;
|
|
|
|
@HiveField(6)
|
|
final String? orderId;
|
|
|
|
@HiveField(7)
|
|
final String? rewardId;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Optimization
|
|
|
|
### Image Caching
|
|
```dart
|
|
// Use cached_network_image for all remote images
|
|
CachedNetworkImage(
|
|
imageUrl: product.images.first,
|
|
placeholder: (context, url) => const ShimmerPlaceholder(),
|
|
errorWidget: (context, url, error) => const Icon(Icons.error),
|
|
fit: BoxFit.cover,
|
|
memCacheWidth: 400, // Optimize memory usage
|
|
fadeInDuration: const Duration(milliseconds: 300),
|
|
)
|
|
```
|
|
|
|
### List Performance
|
|
```dart
|
|
// Use ListView.builder with RepaintBoundary for long lists
|
|
ListView.builder(
|
|
itemCount: items.length,
|
|
itemBuilder: (context, index) {
|
|
return RepaintBoundary(
|
|
child: ProductCard(product: items[index]),
|
|
);
|
|
},
|
|
cacheExtent: 1000, // Pre-render items
|
|
)
|
|
|
|
// Use AutomaticKeepAliveClientMixin for expensive widgets
|
|
class ProductCard extends StatefulWidget {
|
|
@override
|
|
State<ProductCard> createState() => _ProductCardState();
|
|
}
|
|
|
|
class _ProductCardState extends State<ProductCard>
|
|
with AutomaticKeepAliveClientMixin {
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
return Card(...);
|
|
}
|
|
}
|
|
```
|
|
|
|
### State Optimization
|
|
```dart
|
|
// Use .select() to avoid unnecessary rebuilds
|
|
final userName = ref.watch(authProvider.select((state) => state.user?.name));
|
|
|
|
// Use family modifiers for parameterized providers
|
|
@riverpod
|
|
Future<Product> product(ProductRef ref, String id) async {
|
|
return await ref.read(productRepositoryProvider).getProduct(id);
|
|
}
|
|
|
|
// Keep providers outside build method
|
|
final productsProvider = ...;
|
|
|
|
class ProductsPage extends ConsumerWidget {
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final products = ref.watch(productsProvider);
|
|
return ...;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Offline Strategy
|
|
|
|
### Data Sync Flow
|
|
```dart
|
|
@riverpod
|
|
class DataSync extends _$DataSync {
|
|
@override
|
|
Future<SyncStatus> build() async {
|
|
// Listen to connectivity changes
|
|
ref.listen(connectivityProvider, (previous, next) {
|
|
if (next == ConnectivityStatus.connected) {
|
|
syncData();
|
|
}
|
|
});
|
|
|
|
return SyncStatus.idle;
|
|
}
|
|
|
|
Future<void> syncData() async {
|
|
state = const AsyncValue.loading();
|
|
|
|
state = await AsyncValue.guard(() async {
|
|
// Sync in order of dependency
|
|
await _syncUserData();
|
|
await _syncProducts();
|
|
await _syncOrders();
|
|
await _syncProjects();
|
|
await _syncLoyaltyData();
|
|
|
|
await ref.read(settingsRepositoryProvider).updateLastSyncTime();
|
|
|
|
return SyncStatus.success;
|
|
});
|
|
}
|
|
|
|
Future<void> _syncUserData() async {
|
|
final user = await ref.read(authRepositoryProvider).getCurrentUser();
|
|
await ref.read(authLocalDataSourceProvider).saveUser(user);
|
|
}
|
|
|
|
Future<void> _syncProducts() async {
|
|
final products = await ref.read(productRepositoryProvider).getAllProducts();
|
|
await ref.read(productLocalDataSourceProvider).saveProducts(products);
|
|
}
|
|
|
|
// ... other sync methods
|
|
}
|
|
```
|
|
|
|
### Offline Queue
|
|
```dart
|
|
// Queue failed requests for retry when online
|
|
class OfflineQueue {
|
|
final HiveInterface hive;
|
|
late Box<Map> _queueBox;
|
|
|
|
Future<void> init() async {
|
|
_queueBox = await hive.openBox('offline_queue');
|
|
}
|
|
|
|
Future<void> addToQueue(ApiRequest request) async {
|
|
await _queueBox.add({
|
|
'endpoint': request.endpoint,
|
|
'method': request.method,
|
|
'body': request.body,
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
});
|
|
}
|
|
|
|
Future<void> processQueue() async {
|
|
final requests = _queueBox.values.toList();
|
|
|
|
for (var i = 0; i < requests.length; i++) {
|
|
try {
|
|
await _executeRequest(requests[i]);
|
|
await _queueBox.deleteAt(i);
|
|
} catch (e) {
|
|
// Keep in queue for next retry
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Network Errors
|
|
```dart
|
|
class ApiClient {
|
|
Future<T> request<T>(String endpoint, {Map<String, dynamic>? data}) async {
|
|
try {
|
|
final response = await dio.post(endpoint, data: data);
|
|
return _parseResponse<T>(response);
|
|
} on DioException catch (e) {
|
|
if (e.type == DioExceptionType.connectionTimeout) {
|
|
throw NetworkException('Connection timeout. Please check your internet.');
|
|
} else if (e.type == DioExceptionType.receiveTimeout) {
|
|
throw NetworkException('Server took too long to respond.');
|
|
} else if (e.response?.statusCode == 401) {
|
|
throw UnauthorizedException('Session expired. Please login again.');
|
|
} else if (e.response?.statusCode == 404) {
|
|
throw NotFoundException('Resource not found.');
|
|
} else if (e.response?.statusCode == 500) {
|
|
throw ServerException('Server error. Please try again later.');
|
|
} else {
|
|
throw NetworkException('Network error: ${e.message}');
|
|
}
|
|
} catch (e) {
|
|
throw UnknownException('Unexpected error: $e');
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Form Validation
|
|
```dart
|
|
class Validators {
|
|
static String? required(String? value) {
|
|
if (value == null || value.trim().isEmpty) {
|
|
return 'Trường này là bắt buộc';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static String? vietnamesePhone(String? value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Vui lòng nhập số điện thoại';
|
|
}
|
|
|
|
final phoneRegex = RegExp(r'^(0|\+84)[3|5|7|8|9][0-9]{8}$');
|
|
if (!phoneRegex.hasMatch(value)) {
|
|
return 'Số điện thoại không hợp lệ';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static String? email(String? value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Vui lòng nhập email';
|
|
}
|
|
|
|
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
|
if (!emailRegex.hasMatch(value)) {
|
|
return 'Email không hợp lệ';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static String? password(String? value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Vui lòng nhập mật khẩu';
|
|
}
|
|
|
|
if (value.length < 8) {
|
|
return 'Mật khẩu phải có ít nhất 8 ký tự';
|
|
}
|
|
|
|
if (!RegExp(r'[A-Z]').hasMatch(value)) {
|
|
return 'Mật khẩu phải có ít nhất 1 chữ hoa';
|
|
}
|
|
|
|
if (!RegExp(r'[a-z]').hasMatch(value)) {
|
|
return 'Mật khẩu phải có ít nhất 1 chữ thường';
|
|
}
|
|
|
|
if (!RegExp(r'[0-9]').hasMatch(value)) {
|
|
return 'Mật khẩu phải có ít nhất 1 số';
|
|
}
|
|
|
|
if (!RegExp(r'[!@#\$%^&*(),.?":{}|<>]').hasMatch(value)) {
|
|
return 'Mật khẩu phải có ít nhất 1 ký tự đặc biệt';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Tests
|
|
```dart
|
|
void main() {
|
|
group('CartNotifier', () {
|
|
late CartNotifier cartNotifier;
|
|
late MockCartRepository mockRepository;
|
|
|
|
setUp(() {
|
|
mockRepository = MockCartRepository();
|
|
cartNotifier = CartNotifier(mockRepository);
|
|
});
|
|
|
|
test('addItem adds product to cart', () {
|
|
final product = Product(id: '1', name: 'Test', price: 100);
|
|
|
|
cartNotifier.addItem(product, 2);
|
|
|
|
expect(cartNotifier.state.length, 1);
|
|
expect(cartNotifier.state.first.quantity, 2);
|
|
verify(mockRepository.saveCart(any)).called(1);
|
|
});
|
|
|
|
test('cartTotal calculates correct total', () {
|
|
final items = [
|
|
CartItem(id: '1', price: 100, quantity: 2),
|
|
CartItem(id: '2', price: 50, quantity: 3),
|
|
];
|
|
|
|
final total = calculateCartTotal(items);
|
|
|
|
expect(total, 350); // (100 * 2) + (50 * 3)
|
|
});
|
|
});
|
|
}
|
|
```
|
|
|
|
### Widget Tests
|
|
```dart
|
|
void main() {
|
|
testWidgets('ProductCard displays product info', (tester) async {
|
|
final product = Product(
|
|
id: '1',
|
|
name: 'Test Product',
|
|
price: 100,
|
|
images: ['https://example.com/image.jpg'],
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: ProductCard(product: product),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Test Product'), findsOneWidget);
|
|
expect(find.text('100,000 ₫'), findsOneWidget);
|
|
expect(find.byType(CachedNetworkImage), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('OTPInputField auto-focuses next field', (tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: OTPInputField(onCompleted: (otp) {}),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Enter first digit
|
|
await tester.enterText(find.byType(TextField).first, '1');
|
|
await tester.pump();
|
|
|
|
// Verify focus moved to second field
|
|
expect(
|
|
tester.widget<TextField>(find.byType(TextField).at(1)).focusNode?.hasFocus,
|
|
isTrue,
|
|
);
|
|
});
|
|
}
|
|
```
|
|
|
|
### Integration Tests
|
|
```dart
|
|
void main() {
|
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
testWidgets('Complete checkout flow', (tester) async {
|
|
app.main();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Navigate to products
|
|
await tester.tap(find.byIcon(Icons.shopping_bag));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Add product to cart
|
|
await tester.tap(find.byType(ProductCard).first);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('Add to Cart'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Go to cart
|
|
await tester.tap(find.byIcon(Icons.shopping_cart));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Proceed to checkout
|
|
await tester.tap(find.text('Checkout'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Fill delivery info
|
|
await tester.enterText(find.byKey(Key('recipient_name')), 'John Doe');
|
|
await tester.enterText(find.byKey(Key('phone')), '0912345678');
|
|
await tester.enterText(find.byKey(Key('address')), '123 Main St');
|
|
|
|
// Select payment method
|
|
await tester.tap(find.text('Cash on Delivery'));
|
|
|
|
// Place order
|
|
await tester.tap(find.text('Place Order'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify success
|
|
expect(find.text('Order Successful'), findsOneWidget);
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Localization (Vietnamese Primary)
|
|
|
|
### Setup
|
|
```dart
|
|
// l10n.yaml
|
|
arb-dir: lib/l10n
|
|
template-arb-file: app_en.arb
|
|
output-localization-file: app_localizations.dart
|
|
|
|
// lib/l10n/app_vi.arb (Vietnamese)
|
|
{
|
|
"@@locale": "vi",
|
|
"appTitle": "Worker App",
|
|
"login": "Đăng nhập",
|
|
"phone": "Số điện thoại",
|
|
"enterPhone": "Nhập số điện thoại",
|
|
"continue": "Tiếp tục",
|
|
"verifyOTP": "Xác thực OTP",
|
|
"enterOTP": "Nhập mã OTP 6 số",
|
|
"resendOTP": "Gửi lại mã",
|
|
"home": "Trang chủ",
|
|
"products": "Sản phẩm",
|
|
"loyalty": "Hội viên",
|
|
"account": "Tài khoản",
|
|
"points": "Điểm",
|
|
"cart": "Giỏ hàng",
|
|
"checkout": "Thanh toán",
|
|
"orders": "Đơn hàng",
|
|
"projects": "Công trình",
|
|
"quotes": "Báo giá",
|
|
"myGifts": "Quà của tôi",
|
|
"referral": "Giới thiệu bạn bè",
|
|
"pointsHistory": "Lịch sử điểm"
|
|
}
|
|
|
|
// lib/l10n/app_en.arb (English)
|
|
{
|
|
"@@locale": "en",
|
|
"appTitle": "Worker App",
|
|
"login": "Login",
|
|
"phone": "Phone Number",
|
|
"enterPhone": "Enter phone number",
|
|
"continue": "Continue",
|
|
...
|
|
}
|
|
```
|
|
|
|
### Usage
|
|
```dart
|
|
class LoginPage extends ConsumerWidget {
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(l10n.login),
|
|
),
|
|
body: Column(
|
|
children: [
|
|
TextField(
|
|
decoration: InputDecoration(
|
|
labelText: l10n.phone,
|
|
hintText: l10n.enterPhone,
|
|
),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {},
|
|
child: Text(l10n.continue),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
### Android
|
|
```gradle
|
|
// android/app/build.gradle
|
|
android {
|
|
compileSdkVersion 34
|
|
|
|
defaultConfig {
|
|
applicationId "com.eurotile.worker"
|
|
minSdkVersion 21
|
|
targetSdkVersion 34
|
|
versionCode 1
|
|
versionName "1.0.0"
|
|
}
|
|
|
|
signingConfigs {
|
|
release {
|
|
storeFile file(RELEASE_STORE_FILE)
|
|
storePassword RELEASE_STORE_PASSWORD
|
|
keyAlias RELEASE_KEY_ALIAS
|
|
keyPassword RELEASE_KEY_PASSWORD
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
signingConfig signingConfigs.release
|
|
minifyEnabled true
|
|
shrinkResources true
|
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### iOS
|
|
```ruby
|
|
# ios/Podfile
|
|
platform :ios, '13.0'
|
|
|
|
post_install do |installer|
|
|
installer.pods_project.targets.each do |target|
|
|
flutter_additional_ios_build_settings(target)
|
|
target.build_configurations.each do |config|
|
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
---
|
|
|
|
## Development Workflow
|
|
|
|
### Feature Development Process
|
|
1. **Review HTML Reference**: Check `html/` folder for design spec
|
|
2. **Design Data Layer**: Models, repositories, data sources
|
|
3. **Implement Domain Layer**: Entities, use cases
|
|
4. **Create Providers**: Riverpod state management
|
|
5. **Build UI**: Match HTML reference design
|
|
6. **Test**: Unit, widget, integration tests
|
|
7. **Optimize**: Performance profiling
|
|
|
|
### Code Review Checklist
|
|
- [ ] Matches HTML reference design
|
|
- [ ] Follows clean architecture
|
|
- [ ] Proper error handling
|
|
- [ ] Online-first approach
|
|
- [ ] Performance optimized
|
|
- [ ] Proper state management with Riverpod
|
|
- [ ] Hive models properly defined
|
|
- [ ] Vietnamese localization
|
|
- [ ] Tests written and passing
|
|
- [ ] Code documented
|
|
|
|
---
|
|
|
|
## Roadmap
|
|
|
|
### Phase 1 - Complete ✅
|
|
- Authentication (login, OTP, register)
|
|
- Home with member cards (3 tiers)
|
|
- Complete loyalty system
|
|
- Product browsing and cart
|
|
- Order management
|
|
- Project and quote management
|
|
- Chat support
|
|
- Account management
|
|
- Promotions and notifications
|
|
|
|
### Phase 2 - Backend Integration 🔄
|
|
- REST API integration
|
|
- Real-time WebSocket for chat
|
|
- Push notifications (FCM)
|
|
- Payment gateway
|
|
- Cloud image storage
|
|
- Analytics tracking
|
|
|
|
### Phase 3 - Advanced Features 📋
|
|
- QR code scanner (loyalty & products)
|
|
- Barcode scanning
|
|
- Geolocation for nearby stores
|
|
- Photo gallery for projects
|
|
- PDF report generation
|
|
- Biometric authentication
|
|
- Offline mode with background sync
|
|
- AR product visualization
|
|
|
|
---
|
|
|
|
## Remember: ALWAYS DELEGATE TO SPECIALISTS FOR BETTER RESULTS!
|
|
|
|
When working on this Flutter Worker app:
|
|
- **UI Tasks** → flutter-widget-expert
|
|
- **State Management** → riverpod-expert
|
|
- **Database/Caching** → hive-expert
|
|
- **API Integration** → api-integration-expert
|
|
- **Architecture Decisions** → architecture-expert
|
|
- **Performance Issues** → performance-expert
|
|
|
|
**Think delegation first, implementation second!**
|