# FlutterFlow Custom Widgets Guide

This guide covers all the custom widgets available in the FlutterFlow-Supabase Sync Middleware for building rich, interactive UIs.

## 📋 Table of Contents

- [Sync Status Widgets](#sync-status-widgets)
- [Data Display Widgets](#data-display-widgets)
- [File Upload Widgets](#file-upload-widgets)
- [Image Management Widgets](#image-management-widgets)
- [Custom Widget Examples](#custom-widget-examples)

## 🔄 Sync Status Widgets

### SyncStatusWidget

Displays the current sync status with an icon and text.

**Features:**
- Real-time status updates
- Color-coded status indicators
- Automatic icon changes based on sync state

**Usage:**
```dart
// Add to your app bar
AppBar(
  title: Text('My App'),
  actions: [
    SyncStatusWidget(),
  ],
)

// Or use in your page body
Column(
  children: [
    SyncStatusWidget(),
    // Your other widgets
  ],
)
```

**Status Types:**
- 🟢 **Connected** - Successfully connected to server
- 🔴 **Disconnected** - Not connected to server
- 🟠 **Syncing** - Currently syncing data
- ✅ **Synced** - Sync completed successfully
- ❌ **Error** - Sync failed with error

### SyncButtonWidget

A button that triggers manual sync with loading state.

**Features:**
- Shows loading spinner during sync
- Disabled state during sync operations
- Automatic state management

**Usage:**
```dart
// Add to your app bar
AppBar(
  title: Text('My App'),
  actions: [
    SyncButtonWidget(),
  ],
)

// Or use in your page body
Column(
  children: [
    ElevatedButton(
      onPressed: () {
        // Your other actions
      },
      child: Text('Save'),
    ),
    SyncButtonWidget(),
  ],
)
```

### ConnectionStatusWidget

Shows online/offline connection status.

**Features:**
- Real-time connection monitoring
- Compact status indicator
- Automatic updates

**Usage:**
```dart
// Add to your app bar
AppBar(
  title: Text('My App'),
  actions: [
    ConnectionStatusWidget(),
  ],
)

// Or use in a status bar
Container(
  padding: EdgeInsets.all(8),
  child: Row(
    children: [
      ConnectionStatusWidget(),
      Text('App Status'),
    ],
  ),
)
```

### SyncProgressWidget

Shows a progress indicator during sync operations.

**Features:**
- Linear progress bar
- Only visible during sync
- Automatic show/hide

**Usage:**
```dart
Column(
  children: [
    SyncProgressWidget(),
    // Your page content
    Expanded(
      child: YourContentWidget(),
    ),
  ],
)
```

## 📊 Data Display Widgets

### SyncDataListWidget

A list widget that automatically syncs and displays data.

**Features:**
- Automatic data loading
- Real-time updates
- Pull-to-refresh support
- Error handling
- Loading states

**Parameters:**
- `tableName` (String, required) - Name of the table to display
- `itemBuilder` (Widget Function, required) - Function to build list items
- `where` (String, optional) - WHERE clause for filtering
- `limit` (int, optional) - Maximum number of items
- `orderBy` (String, optional) - ORDER BY clause
- `showSyncStatus` (bool, optional) - Show sync status (default: true)

**Usage:**
```dart
SyncDataListWidget(
  tableName: 'store_user_link',
  itemBuilder: (context, item) => ListTile(
    leading: Icon(Icons.store),
    title: Text(item['name'] ?? 'No Name'),
    subtitle: Text(item['store_code'] ?? 'No Code'),
    trailing: IconButton(
      icon: Icon(Icons.delete),
      onPressed: () async {
        await DeleteData(
          tableName: 'store_user_link',
          recordId: item['id'],
        );
      },
    ),
  ),
  where: 'user_id = ?',
  whereArgs: [currentUserId],
  orderBy: 'name ASC',
)
```

**Advanced Example:**
```dart
SyncDataListWidget(
  tableName: 'task',
  itemBuilder: (context, item) => Card(
    child: ListTile(
      leading: CircleAvatar(
        backgroundColor: _getStatusColor(item['status']),
        child: Icon(_getStatusIcon(item['status'])),
      ),
      title: Text(item['name'] ?? 'No Name'),
      subtitle: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Store: ${item['store_name'] ?? 'Unknown'}'),
          Text('Category: ${item['category'] ?? 'Unknown'}'),
          Text('Created: ${_formatDate(item['created_at'])}'),
        ],
      ),
      trailing: PopupMenuButton(
        itemBuilder: (context) => [
          PopupMenuItem(
            value: 'edit',
            child: Text('Edit'),
          ),
          PopupMenuItem(
            value: 'delete',
            child: Text('Delete'),
          ),
        ],
        onSelected: (value) {
          if (value == 'edit') {
            _editTask(item);
          } else if (value == 'delete') {
            _deleteTask(item['id']);
          }
        },
      ),
    ),
  ),
  where: 'status = ? AND store_code = ?',
  whereArgs: ['pending', selectedStoreCode],
  orderBy: 'created_at DESC',
  limit: 50,
)
```

## 📁 File Upload Widgets

### FileUploadWidget

A widget for uploading files with drag-and-drop support.

**Features:**
- File type validation
- Multiple file support
- Upload progress
- Error handling
- Custom styling

**Parameters:**
- `onFileSelected` (Function(File), required) - Callback when file is selected
- `hintText` (String, optional) - Hint text to display
- `allowedExtensions` (List<String>, optional) - Allowed file extensions
- `multiple` (bool, optional) - Allow multiple files (default: false)

**Usage:**
```dart
FileUploadWidget(
  onFileSelected: (file) async {
    try {
      final result = await UploadFile(file: file);
      if (result != null) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('File uploaded successfully!')),
        );
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Upload failed: $e')),
      );
    }
  },
  hintText: 'Tap to upload document',
  allowedExtensions: ['.pdf', '.doc', '.docx'],
)
```

**Multiple Files Example:**
```dart
FileUploadWidget(
  onFileSelected: (file) async {
    // Handle single file selection
    final result = await UploadFile(file: file);
    // Process result
  },
  multiple: true,
  hintText: 'Tap to upload multiple files',
  allowedExtensions: ['.pdf', '.jpg', '.png', '.doc'],
)
```

## 🖼️ Image Management Widgets

### ImageUploadWidget

A specialized widget for uploading images with preview.

**Features:**
- Image preview
- Automatic image processing
- Thumbnail generation
- Upload progress
- Error handling

**Parameters:**
- `onImageSelected` (Function(File), required) - Callback when image is selected
- `hintText` (String, optional) - Hint text to display
- `width` (double, optional) - Widget width (default: 200)
- `height` (double, optional) - Widget height (default: 200)

**Usage:**
```dart
ImageUploadWidget(
  onImageSelected: (image) async {
    try {
      final result = await UploadImage(imageFile: image);
      if (result != null) {
        // Save image reference to your data
        await WriteSyncData(
          tableName: 'product_images',
          operation: 'INSERT',
          data: {
            'id': 'img_${DateTime.now().millisecondsSinceEpoch}',
            'product_id': productId,
            'image_url': result['fileUrl'],
            'thumbnail_url': result['thumbnailUrl'],
          },
        );
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Image upload failed: $e')),
      );
    }
  },
  hintText: 'Tap to upload product image',
  width: 300,
  height: 200,
)
```

### ImageGalleryWidget

A grid widget for displaying uploaded images.

**Features:**
- Grid layout
- Image thumbnails
- Upload button
- Error handling
- Loading states

**Parameters:**
- `tableName` (String, required) - Name of the table containing images
- `imageBuilder` (Widget Function, optional) - Custom image builder
- `showUploadButton` (bool, optional) - Show upload button (default: true)
- `onUploadPressed` (Function, optional) - Callback for upload button

**Usage:**
```dart
ImageGalleryWidget(
  tableName: 'product_images',
  imageBuilder: (context, image) => GestureDetector(
    onTap: () {
      // Show full-size image
      _showImageDialog(image['fileUrl']);
    },
    child: Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(8),
        image: DecorationImage(
          image: NetworkImage(image['fileUrl']),
          fit: BoxFit.cover,
        ),
      ),
      child: Stack(
        children: [
          Positioned(
            top: 4,
            right: 4,
            child: IconButton(
              icon: Icon(Icons.delete, color: Colors.red),
              onPressed: () async {
                await DeleteUploadedFile(fileId: image['id']);
              },
            ),
          ),
        ],
      ),
    ),
  ),
  onUploadPressed: () {
    // Navigate to image upload page
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => ImageUploadPage()),
    );
  },
)
```

## 🎨 Custom Widget Examples

### Product Management Widget

A complete product management widget with image upload and data sync.

```dart
class ProductManagementWidget extends StatefulWidget {
  final String productId;
  
  const ProductManagementWidget({Key? key, required this.productId}) : super(key: key);
  
  @override
  _ProductManagementWidgetState createState() => _ProductManagementWidgetState();
}

class _ProductManagementWidgetState extends State<ProductManagementWidget> {
  final _nameController = TextEditingController();
  final _descriptionController = TextEditingController();
  final _priceController = TextEditingController();
  List<Map<String, dynamic>> _images = [];
  bool _isLoading = false;
  
  @override
  void initState() {
    super.initState();
    _loadProduct();
    _loadImages();
  }
  
  Future<void> _loadProduct() async {
    setState(() => _isLoading = true);
    
    try {
      final products = await ReadSyncData(
        tableName: 'product_store',
        where: 'id = ?',
        whereArgs: [widget.productId],
      );
      
      if (products.isNotEmpty) {
        final product = products.first;
        _nameController.text = product['name'] ?? '';
        _descriptionController.text = product['description'] ?? '';
        _priceController.text = product['price']?.toString() ?? '';
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to load product: $e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }
  
  Future<void> _loadImages() async {
    try {
      final images = await ReadSyncData(
        tableName: 'product_images',
        where: 'product_id = ?',
        whereArgs: [widget.productId],
      );
      
      setState(() {
        _images = images;
      });
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to load images: $e')),
      );
    }
  }
  
  Future<void> _saveProduct() async {
    if (_nameController.text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Product name is required')),
      );
      return;
    }
    
    try {
      await WriteSyncData(
        tableName: 'product_store',
        operation: 'UPDATE',
        data: {
          'id': widget.productId,
          'name': _nameController.text,
          'description': _descriptionController.text,
          'price': double.tryParse(_priceController.text) ?? 0.0,
          'updated_at': DateTime.now().toIso8601String(),
        },
      );
      
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Product saved successfully!')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to save product: $e')),
      );
    }
  }
  
  Future<void> _uploadImage(File image) async {
    try {
      final result = await UploadImage(imageFile: image);
      if (result != null) {
        await WriteSyncData(
          tableName: 'product_images',
          operation: 'INSERT',
          data: {
            'id': 'img_${DateTime.now().millisecondsSinceEpoch}',
            'product_id': widget.productId,
            'image_url': result['fileUrl'],
            'thumbnail_url': result['thumbnailUrl'],
            'created_at': DateTime.now().toIso8601String(),
          },
        );
        
        _loadImages(); // Refresh images
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to upload image: $e')),
      );
    }
  }
  
  @override
  Widget build(BuildContext context) {
    if (_isLoading) {
      return Center(child: CircularProgressIndicator());
    }
    
    return Scaffold(
      appBar: AppBar(
        title: Text('Product Management'),
        actions: [
          SyncStatusWidget(),
          SyncButtonWidget(),
        ],
      ),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Product form
            Card(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Product Information',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    SizedBox(height: 16),
                    TextField(
                      controller: _nameController,
                      decoration: InputDecoration(
                        labelText: 'Product Name',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16),
                    TextField(
                      controller: _descriptionController,
                      decoration: InputDecoration(
                        labelText: 'Description',
                        border: OutlineInputBorder(),
                      ),
                      maxLines: 3,
                    ),
                    SizedBox(height: 16),
                    TextField(
                      controller: _priceController,
                      decoration: InputDecoration(
                        labelText: 'Price',
                        border: OutlineInputBorder(),
                      ),
                      keyboardType: TextInputType.number,
                    ),
                    SizedBox(height: 16),
                    ElevatedButton(
                      onPressed: _saveProduct,
                      child: Text('Save Product'),
                    ),
                  ],
                ),
              ),
            ),
            
            SizedBox(height: 16),
            
            // Image upload section
            Card(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Product Images',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    SizedBox(height: 16),
                    ImageUploadWidget(
                      onImageSelected: _uploadImage,
                      hintText: 'Upload product image',
                      width: double.infinity,
                      height: 200,
                    ),
                    SizedBox(height: 16),
                    Text(
                      'Uploaded Images (${_images.length})',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    SizedBox(height: 8),
                    GridView.builder(
                      shrinkWrap: true,
                      physics: NeverScrollableScrollPhysics(),
                      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 3,
                        crossAxisSpacing: 8,
                        mainAxisSpacing: 8,
                      ),
                      itemCount: _images.length,
                      itemBuilder: (context, index) {
                        final image = _images[index];
                        return GestureDetector(
                          onTap: () {
                            // Show full-size image
                            _showImageDialog(image['image_url']);
                          },
                          child: Container(
                            decoration: BoxDecoration(
                              borderRadius: BorderRadius.circular(8),
                              image: DecorationImage(
                                image: NetworkImage(image['thumbnail_url'] ?? image['image_url']),
                                fit: BoxFit.cover,
                              ),
                            ),
                            child: Stack(
                              children: [
                                Positioned(
                                  top: 4,
                                  right: 4,
                                  child: IconButton(
                                    icon: Icon(Icons.delete, color: Colors.red, size: 20),
                                    onPressed: () async {
                                      await DeleteUploadedFile(fileId: image['id']);
                                      _loadImages(); // Refresh images
                                    },
                                  ),
                                ),
                              ],
                            ),
                          ),
                        );
                      },
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
  
  void _showImageDialog(String imageUrl) {
    showDialog(
      context: context,
      builder: (context) => Dialog(
        child: Container(
          width: double.infinity,
          height: 400,
          decoration: BoxDecoration(
            image: DecorationImage(
              image: NetworkImage(imageUrl),
              fit: BoxFit.contain,
            ),
          ),
        ),
      ),
    );
  }
}
```

### Task Management Widget

A task management widget with sync capabilities.

```dart
class TaskManagementWidget extends StatefulWidget {
  final String storeCode;
  
  const TaskManagementWidget({Key? key, required this.storeCode}) : super(key: key);
  
  @override
  _TaskManagementWidgetState createState() => _TaskManagementWidgetState();
}

class _TaskManagementWidgetState extends State<TaskManagementWidget> {
  List<Map<String, dynamic>> _tasks = [];
  bool _isLoading = false;
  
  @override
  void initState() {
    super.initState();
    _loadTasks();
  }
  
  Future<void> _loadTasks() async {
    setState(() => _isLoading = true);
    
    try {
      final tasks = await ReadSyncData(
        tableName: 'task',
        where: 'store_code = ?',
        whereArgs: [widget.storeCode],
        orderBy: 'created_at DESC',
      );
      
      setState(() {
        _tasks = tasks;
      });
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to load tasks: $e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }
  
  Future<void> _updateTaskStatus(String taskId, String status) async {
    try {
      await UpdateData(
        tableName: 'task',
        data: {
          'id': taskId,
          'status': status,
          'updated_at': DateTime.now().toIso8601String(),
        },
      );
      
      _loadTasks(); // Refresh tasks
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to update task: $e')),
      );
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Tasks - ${widget.storeCode}'),
        actions: [
          SyncStatusWidget(),
          SyncButtonWidget(),
        ],
      ),
      body: _isLoading
          ? Center(child: CircularProgressIndicator())
          : RefreshIndicator(
              onRefresh: _loadTasks,
              child: SyncDataListWidget(
                tableName: 'task',
                itemBuilder: (context, item) => Card(
                  child: ListTile(
                    leading: CircleAvatar(
                      backgroundColor: _getStatusColor(item['status']),
                      child: Icon(_getStatusIcon(item['status'])),
                    ),
                    title: Text(item['name'] ?? 'No Name'),
                    subtitle: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text('Category: ${item['category'] ?? 'Unknown'}'),
                        Text('Brand: ${item['brand'] ?? 'Unknown'}'),
                        Text('Product: ${item['product'] ?? 'Unknown'}'),
                        Text('Created: ${_formatDate(item['created_at'])}'),
                      ],
                    ),
                    trailing: PopupMenuButton(
                      itemBuilder: (context) => [
                        PopupMenuItem(
                          value: 'complete',
                          child: Text('Mark Complete'),
                        ),
                        PopupMenuItem(
                          value: 'pending',
                          child: Text('Mark Pending'),
                        ),
                        PopupMenuItem(
                          value: 'delete',
                          child: Text('Delete'),
                        ),
                      ],
                      onSelected: (value) {
                        if (value == 'complete') {
                          _updateTaskStatus(item['id'], 'completed');
                        } else if (value == 'pending') {
                          _updateTaskStatus(item['id'], 'pending');
                        } else if (value == 'delete') {
                          _deleteTask(item['id']);
                        }
                      },
                    ),
                  ),
                ),
                where: 'store_code = ?',
                whereArgs: [widget.storeCode],
                orderBy: 'created_at DESC',
              ),
            ),
    );
  }
  
  Color _getStatusColor(String? status) {
    switch (status) {
      case 'completed':
        return Colors.green;
      case 'pending':
        return Colors.orange;
      case 'cancelled':
        return Colors.red;
      default:
        return Colors.grey;
    }
  }
  
  IconData _getStatusIcon(String? status) {
    switch (status) {
      case 'completed':
        return Icons.check;
      case 'pending':
        return Icons.pending;
      case 'cancelled':
        return Icons.cancel;
      default:
        return Icons.help;
    }
  }
  
  String _formatDate(String? dateString) {
    if (dateString == null) return 'Unknown';
    try {
      final date = DateTime.parse(dateString);
      return '${date.day}/${date.month}/${date.year}';
    } catch (e) {
      return 'Invalid Date';
    }
  }
  
  Future<void> _deleteTask(String taskId) async {
    try {
      await DeleteData(
        tableName: 'task',
        recordId: taskId,
      );
      
      _loadTasks(); // Refresh tasks
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to delete task: $e')),
      );
    }
  }
}
```

## 🎯 Best Practices

### 1. Error Handling

Always wrap widget operations in try-catch blocks:

```dart
try {
  final data = await ReadSyncData(tableName: 'my_table');
  // Use data
} catch (e) {
  // Show error to user
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Error: $e')),
  );
}
```

### 2. Loading States

Show loading indicators during operations:

```dart
bool _isLoading = false;

Future<void> _loadData() async {
  setState(() => _isLoading = true);
  
  try {
    // Load data
  } finally {
    setState(() => _isLoading = false);
  }
}
```

### 3. Real-time Updates

Listen for sync events to update your UI:

```dart
@override
void initState() {
  super.initState();
  
  // Listen for sync events
  FlutterFlowSyncClient.instance.eventStream.listen((event) {
    if (event is DataUpdated) {
      // Refresh your data
      _loadData();
    }
  });
}
```

### 4. Optimize Performance

Use appropriate limits and pagination:

```dart
SyncDataListWidget(
  tableName: 'large_table',
  limit: 50, // Limit results
  orderBy: 'created_at DESC', // Order by most recent
  // ... other parameters
)
```

This guide covers all the custom widgets available in the FlutterFlow-Supabase Sync Middleware. Use these widgets to build powerful, offline-first applications with rich user interfaces!
