md
This commit is contained in:
349
CLAUDE.md
349
CLAUDE.md
@@ -81,12 +81,92 @@ You have access to these expert subagents - USE THEM PROACTIVELY:
|
||||
- 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
|
||||
- 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
|
||||
|
||||
### Hive Best Practices
|
||||
**IMPORTANT: Box Type Management**
|
||||
When working with Hive boxes, always use `Box<dynamic>` in data sources and apply `.whereType<T>()` for type-safe queries:
|
||||
|
||||
```dart
|
||||
// ✅ CORRECT - Use Box<dynamic> with type filtering
|
||||
Box<dynamic> get _box {
|
||||
return Hive.box<dynamic>(HiveBoxNames.favoriteBox);
|
||||
}
|
||||
|
||||
Future<List<FavoriteModel>> getAllFavorites(String userId) async {
|
||||
try {
|
||||
final favorites = _box.values
|
||||
.whereType<FavoriteModel>() // Type-safe filtering
|
||||
.where((fav) => fav.userId == userId)
|
||||
.toList();
|
||||
return favorites;
|
||||
} catch (e) {
|
||||
debugPrint('[DataSource] Error: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ INCORRECT - Will cause HiveError
|
||||
Box<FavoriteModel> get _box {
|
||||
return Hive.box<FavoriteModel>(HiveBoxNames.favoriteBox);
|
||||
}
|
||||
```
|
||||
|
||||
**Reason**: Hive boxes are opened as `Box<dynamic>` in the central HiveService. Re-opening with a specific type causes `HiveError: The box is already open and of type Box<dynamic>`.
|
||||
|
||||
### AppBar Standardization
|
||||
**ALL AppBars must follow this standard pattern** (reference: `products_page.dart`):
|
||||
|
||||
```dart
|
||||
AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
title: const Text('Page Title', style: TextStyle(color: Colors.black)),
|
||||
elevation: AppBarSpecs.elevation,
|
||||
backgroundColor: AppColors.white,
|
||||
foregroundColor: AppColors.grey900,
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
// Custom actions here
|
||||
const SizedBox(width: AppSpacing.sm), // Always end with spacing
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
**Key Requirements**:
|
||||
- Black back arrow with explicit color
|
||||
- Black text title with TextStyle
|
||||
- Left-aligned title (`centerTitle: false`)
|
||||
- White background (`AppColors.white`)
|
||||
- Use `AppBarSpecs.elevation` (not hardcoded values)
|
||||
- Always add `SizedBox(width: AppSpacing.sm)` after actions
|
||||
|
||||
**For SliverAppBar** (in CustomScrollView):
|
||||
```dart
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
backgroundColor: AppColors.white,
|
||||
foregroundColor: AppColors.grey900,
|
||||
elevation: AppBarSpecs.elevation,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
title: const Text('Page Title', style: TextStyle(color: Colors.black)),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
// Custom actions
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Worker App Project Structure
|
||||
@@ -593,6 +673,10 @@ final otpTimerProvider = StateNotifierProvider<OTPTimerNotifier, int>
|
||||
- **Quick Action Grid**:
|
||||
- 4-6 icon buttons for main features
|
||||
- Products, Loyalty, Orders, Projects, Promotions
|
||||
- **Cart badge**: Dynamic count from `cartItemCountProvider`
|
||||
- Shows cart item count when > 0
|
||||
- Hidden when cart is empty
|
||||
- Updates in real-time across all pages
|
||||
|
||||
- **Bottom Navigation**:
|
||||
- 5 tabs: Home, Products, Loyalty, Account, More
|
||||
@@ -657,27 +741,55 @@ final loyaltyPointsProvider = AsyncNotifierProvider<LoyaltyPointsNotifier, Loyal
|
||||
|
||||
---
|
||||
|
||||
#### Rewards Page
|
||||
#### Rewards Page ✅ IMPLEMENTED
|
||||
**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
|
||||
- **Points Balance Card**: Gradient blue card showing available points and expiring points
|
||||
- **Category Filter Pills**: Horizontal scrollable chips (Tất cả/Voucher/Sản phẩm/Dịch vụ/Ưu đãi đặc biệt)
|
||||
- **Gift Grid**: 2-column grid with 6 mock gifts
|
||||
- **Reward Cards** (improved UI):
|
||||
- 120px image with CachedNetworkImage
|
||||
- Title and description (truncated)
|
||||
- Points cost and button **at bottom** (using Spacer())
|
||||
- Button states: "Đổi quà" (enabled) or "Không đủ điểm" (disabled)
|
||||
- **Redemption Flow**:
|
||||
- Confirmation dialog with gift details and balance after redemption
|
||||
- Success SnackBar with checkmark icon
|
||||
- Points deduction via `loyaltyPointsProvider`
|
||||
|
||||
**Widgets**:
|
||||
- `RewardCard`: Card with image, info, and redeem button
|
||||
- `RewardRedemptionDialog`: Confirmation dialog
|
||||
- `GiftCodeDisplay`: Copy-able gift code after redemption
|
||||
**Widgets** (Location: `lib/features/loyalty/presentation/`):
|
||||
- `widgets/points_balance_card.dart`: Gradient card with points display
|
||||
- `widgets/reward_card.dart`: Individual gift card with bottom-aligned action
|
||||
- `pages/rewards_page.dart`: Main rewards screen
|
||||
|
||||
**Design Reference**: `html/loyalty-rewards.html`
|
||||
**State Management**:
|
||||
```dart
|
||||
// Providers in lib/features/loyalty/presentation/providers/
|
||||
@riverpod
|
||||
class LoyaltyPoints extends _$LoyaltyPoints {
|
||||
// Manages 9,750 available points, 1,200 expiring
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class Gifts extends _$Gifts {
|
||||
// 6 mock gifts matching HTML design
|
||||
}
|
||||
|
||||
@riverpod
|
||||
List<GiftCatalog> filteredGifts(ref) {
|
||||
// Filters by selected category
|
||||
}
|
||||
|
||||
final selectedGiftCategoryProvider = StateNotifierProvider...
|
||||
final hasEnoughPointsProvider = Provider.family<bool, int>...
|
||||
```
|
||||
|
||||
**Navigation**:
|
||||
- Route: `/loyalty/rewards` (RouteNames in app_router.dart)
|
||||
- Accessible from home quick action "Đổi quà"
|
||||
|
||||
**Design Reference**: `html/loyalty-rewards.html` ✅
|
||||
|
||||
---
|
||||
|
||||
@@ -1275,6 +1387,46 @@ class FABSpecs {
|
||||
}
|
||||
```
|
||||
|
||||
#### AppBar (Standardized across all pages)
|
||||
```dart
|
||||
class AppBarSpecs {
|
||||
// From ui_constants.dart
|
||||
static const double elevation = 0.5;
|
||||
|
||||
// Standard pattern for all pages
|
||||
static AppBar standard({
|
||||
required String title,
|
||||
required VoidCallback onBack,
|
||||
List<Widget>? actions,
|
||||
}) {
|
||||
return AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||
onPressed: onBack,
|
||||
),
|
||||
title: Text(title, style: const TextStyle(color: Colors.black)),
|
||||
elevation: elevation,
|
||||
backgroundColor: AppColors.white,
|
||||
foregroundColor: AppColors.grey900,
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
...?actions,
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage Notes**:
|
||||
- ALL pages use this standard AppBar pattern
|
||||
- Back arrow is always black with explicit color
|
||||
- Title is always left-aligned (`centerTitle: false`)
|
||||
- Title text is always black
|
||||
- Background is always white
|
||||
- Actions always end with `SizedBox(width: AppSpacing.sm)` spacing
|
||||
- For SliverAppBar, add `pinned: true` property
|
||||
|
||||
---
|
||||
|
||||
## Technical Architecture
|
||||
@@ -1761,16 +1913,32 @@ end
|
||||
|
||||
## 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 1 - Core Features ✅
|
||||
- ✅ Authentication (login, OTP, register)
|
||||
- ✅ Home with member cards (3 tiers)
|
||||
- ✅ Product browsing with search and filters
|
||||
- ✅ Shopping cart with dynamic badge
|
||||
- ✅ Favorites system with Hive persistence
|
||||
- ✅ Order management
|
||||
- ✅ Project and quote management
|
||||
- ✅ Account management
|
||||
- ✅ Promotions with detail pages
|
||||
|
||||
### Phase 1.5 - Loyalty System ✅ RECENT
|
||||
- ✅ **Rewards/Gift Redemption Page** (Dec 2024)
|
||||
- Points balance card with gradient
|
||||
- Category filtering (Voucher/Product/Service/Discount)
|
||||
- 2-column gift grid with 6 mock items
|
||||
- Redemption flow with confirmation dialog
|
||||
- Points deduction and success feedback
|
||||
- ✅ **UI Standardization** (Dec 2024)
|
||||
- All AppBars now follow consistent pattern
|
||||
- Dynamic cart badge across all pages
|
||||
- Hive Box<dynamic> best practices documented
|
||||
- 🔄 Points history page (planned)
|
||||
- 🔄 Referral program page (planned)
|
||||
- 🔄 My gifts page (planned)
|
||||
- 🔄 Chat support (planned)
|
||||
|
||||
### Phase 2 - Backend Integration 🔄
|
||||
- REST API integration
|
||||
@@ -1803,3 +1971,130 @@ When working on this Flutter Worker app:
|
||||
- **Performance Issues** → performance-expert
|
||||
|
||||
**Think delegation first, implementation second!**
|
||||
|
||||
---
|
||||
|
||||
## Recent Updates & Implementation Notes (December 2024)
|
||||
|
||||
### ✅ Rewards/Gift Redemption System
|
||||
**Status**: Fully implemented with mock data
|
||||
|
||||
**Implementation Details**:
|
||||
1. **Providers** (`lib/features/loyalty/presentation/providers/`):
|
||||
- `loyalty_points_provider.dart` - Manages user points (9,750 available, 1,200 expiring)
|
||||
- `gifts_provider.dart` - 6 mock gifts matching HTML design
|
||||
- `selectedGiftCategoryProvider` - Category filter state
|
||||
- `filteredGiftsProvider` - Category-based filtering
|
||||
- `hasEnoughPointsProvider.family` - Points validation per gift
|
||||
|
||||
2. **Widgets** (`lib/features/loyalty/presentation/widgets/`):
|
||||
- `points_balance_card.dart` - Gradient blue card with points display
|
||||
- `reward_card.dart` - Gift card with bottom-aligned action button
|
||||
|
||||
3. **Pages** (`lib/features/loyalty/presentation/pages/`):
|
||||
- `rewards_page.dart` - Main screen with RefreshIndicator
|
||||
|
||||
4. **Route**: `/loyalty/rewards` in `app_router.dart`
|
||||
|
||||
**Key Features**:
|
||||
- 2-column grid layout with `childAspectRatio: 0.7`
|
||||
- Category filtering: Tất cả/Voucher/Sản phẩm/Dịch vụ/Ưu đãi đặc biệt
|
||||
- Bottom-aligned buttons using `Expanded` + `Spacer()`
|
||||
- Redemption dialog with points calculation
|
||||
- Success feedback with SnackBar
|
||||
|
||||
### ✅ AppBar Standardization
|
||||
**Status**: Completed across all pages
|
||||
|
||||
**Standard Pattern**:
|
||||
```dart
|
||||
AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
title: const Text('Title', style: TextStyle(color: Colors.black)),
|
||||
elevation: AppBarSpecs.elevation,
|
||||
backgroundColor: AppColors.white,
|
||||
foregroundColor: AppColors.grey900,
|
||||
centerTitle: false,
|
||||
actions: [..., const SizedBox(width: AppSpacing.sm)],
|
||||
)
|
||||
```
|
||||
|
||||
**Updated Pages**:
|
||||
- `cart_page.dart` - Lines 84-103
|
||||
- `favorites_page.dart` - Lines 79-115
|
||||
- `rewards_page.dart` - Lines 35-48
|
||||
- `promotion_detail_page.dart` - Lines 59-86 (loading/error), 130-161 (SliverAppBar)
|
||||
- `product_detail_page.dart` - Already correct ✅
|
||||
|
||||
### ✅ Dynamic Cart Badge
|
||||
**Status**: Implemented across home and products pages
|
||||
|
||||
**Implementation**:
|
||||
```dart
|
||||
// Added provider in cart_provider.dart
|
||||
@riverpod
|
||||
int cartItemCount(CartItemCountRef ref) {
|
||||
final cartState = ref.watch(cartProvider);
|
||||
return cartState.items.fold(0, (sum, item) => sum + item.quantity);
|
||||
}
|
||||
|
||||
// Used in home_page.dart and products_page.dart
|
||||
final cartItemCount = ref.watch(cartItemCountProvider);
|
||||
QuickAction(
|
||||
badge: cartItemCount > 0 ? '$cartItemCount' : null,
|
||||
)
|
||||
```
|
||||
|
||||
**Behavior**:
|
||||
- Shows total quantity across all cart items
|
||||
- Hidden when cart is empty
|
||||
- Updates in real-time via Riverpod
|
||||
- Navigation via `RouteNames.cart` constant
|
||||
|
||||
### ✅ Hive Box Type Management
|
||||
**Status**: Best practices documented and implemented
|
||||
|
||||
**Problem Solved**:
|
||||
```
|
||||
HiveError: The box "favorite_box" is already open and of type Box<dynamic>
|
||||
```
|
||||
|
||||
**Solution Applied**:
|
||||
- All data sources now use `Box<dynamic>` getters
|
||||
- Type-safe queries via `.whereType<T>()`
|
||||
- Applied to `favorites_local_datasource.dart`
|
||||
|
||||
**Pattern**:
|
||||
```dart
|
||||
Box<dynamic> get _box => Hive.box<dynamic>(boxName);
|
||||
|
||||
Future<List<FavoriteModel>> getAllFavorites() async {
|
||||
return _box.values
|
||||
.whereType<FavoriteModel>() // Type-safe!
|
||||
.where((fav) => fav.userId == userId)
|
||||
.toList();
|
||||
}
|
||||
```
|
||||
|
||||
### 🔄 Next Steps (Planned)
|
||||
1. Points history page with transaction list
|
||||
2. Referral program page with share functionality
|
||||
3. My gifts page with tabs (Active/Used/Expired)
|
||||
4. Backend API integration for loyalty endpoints
|
||||
5. Real redemption flow with gift codes
|
||||
|
||||
### 📝 Code Quality Checklist
|
||||
All recent implementations follow:
|
||||
- ✅ Clean architecture (data/domain/presentation)
|
||||
- ✅ Riverpod state management
|
||||
- ✅ Hive for local persistence
|
||||
- ✅ Material 3 design system
|
||||
- ✅ Vietnamese localization
|
||||
- ✅ AppBar standardization
|
||||
- ✅ CachedNetworkImage for all remote images
|
||||
- ✅ Proper error handling
|
||||
- ✅ Loading states (CircularProgressIndicator)
|
||||
- ✅ Empty states with helpful messages
|
||||
|
||||
Reference in New Issue
Block a user