# Flutter Code Examples & Patterns

This document contains all Dart code examples and patterns referenced in `CLAUDE.md`. Use these as templates when implementing features in the Worker app.

---

## Table of Contents
- [Best Practices](#best-practices)
- [UI/UX Components](#uiux-components)
- [State Management](#state-management)
- [Performance Optimization](#performance-optimization)
- [Offline Strategy](#offline-strategy)
- [Localization](#localization)
- [Deployment](#deployment)

---

## Best Practices

### Hive Box Type Management

**✅ CORRECT - Use Box<dynamic> with type filtering**
```dart
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;
  }
}

Future<List<FavoriteModel>> getAllFavorites() async {
  return _box.values
    .whereType<FavoriteModel>()  // Type-safe!
    .where((fav) => fav.userId == userId)
    .toList();
}
```

**❌ INCORRECT - Will cause HiveError**
```dart
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

**Standard AppBar 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
  ],
)
```

**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),
  ],
)
```

**Standard Pattern (Recent Implementation)**:
```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)],
)
```

---

## UI/UX Components

### 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,
  );
}
```

### 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
}
```

### AppBar Specifications

```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),
      ],
    );
  }
}
```

---

## State Management

### Authentication Providers

```dart
final authProvider = AsyncNotifierProvider<AuthNotifier, AuthState>
final otpTimerProvider = StateNotifierProvider<OTPTimerNotifier, int>
```

### Home Providers

```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),
  );
});
```

### Loyalty Providers

```dart
final loyaltyPointsProvider = AsyncNotifierProvider<LoyaltyPointsNotifier, LoyaltyPoints>
```

**Rewards Page Providers**:
```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>...
```

### Referral Provider

```dart
final referralProvider = AsyncNotifierProvider<ReferralNotifier, Referral>
```

### Products Providers

```dart
final productsProvider = AsyncNotifierProvider<ProductsNotifier, List<Product>>
final productSearchProvider = StateProvider<String>
final selectedCategoryProvider = StateProvider<String?>
```

### Cart Providers

```dart
final cartProvider = NotifierProvider<CartNotifier, List<CartItem>>
final cartTotalProvider = Provider<double>
```

**Dynamic Cart Badge**:
```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,
)
```

### Orders Providers

```dart
final ordersProvider = AsyncNotifierProvider<OrdersNotifier, List<Order>>
final orderFilterProvider = StateProvider<OrderStatus?>
final paymentsProvider = AsyncNotifierProvider<PaymentsNotifier, List<Payment>>
```

### Projects Providers

```dart
final projectsProvider = AsyncNotifierProvider<ProjectsNotifier, List<Project>>
final projectFormProvider = StateNotifierProvider<ProjectFormNotifier, ProjectFormState>
```

### Chat Providers

```dart
final chatProvider = AsyncNotifierProvider<ChatNotifier, ChatRoom>
final messagesProvider = StreamProvider<List<Message>>
final typingIndicatorProvider = StateProvider<bool>
```

### Authentication State Implementation

```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);
    });
  }
}
```

---

## 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
      }
    }
  }
}
```

---

## Localization

### 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
```

---

## Quick Reference

### Key Requirements for All Code

- ✅ 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, add `pinned: true` property
- ✅ Use `Box<dynamic>` for Hive boxes with `.whereType<T>()` filtering
- ✅ Clean architecture (data/domain/presentation)
- ✅ Riverpod state management
- ✅ Hive for local persistence
- ✅ Material 3 design system
- ✅ Vietnamese localization
- ✅ CachedNetworkImage for all remote images
- ✅ Proper error handling
- ✅ Loading states (CustomLoadingIndicator)
- ✅ Empty states with helpful messages
