custom_roi_cells 0.3.5 copy "custom_roi_cells: ^0.3.5" to clipboard
custom_roi_cells: ^0.3.5 copied to clipboard

Flutter package for creating grid cells with customizable screen size and number of cells for ROI camera applications, data tables, Excel-like spreadsheets, and more!

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:custom_roi_cells/custom_roi_cells.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Custom ROI Cells Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MainPage(),
    );
  }
}

class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Custom ROI Cells'),
        bottom: TabBar(
          controller: _tabController,
          tabs: const [
            Tab(text: 'ROI Selection', icon: Icon(Icons.grid_on)),
            Tab(text: 'Excel Table', icon: Icon(Icons.table_chart)),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: const [ROISelectionPage(), ExcelTablePage()],
      ),
    );
  }
}

/// ROI Selection Page
class ROISelectionPage extends StatefulWidget {
  const ROISelectionPage({super.key});

  @override
  State<ROISelectionPage> createState() => _ROISelectionPageState();
}

class _ROISelectionPageState extends State<ROISelectionPage> {
  final CellsController controller = CellsController(
    screenWidth: 600.0,
    screenHeight: 800.0,
    cellsRows: 25,
    cellsColumns: 15,
  );

  List<int> selectedIndices = [];

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            padding: const EdgeInsets.all(16.0),
            decoration: BoxDecoration(
              color: Colors.blue.withOpacity(0.1),
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: Colors.blue),
            ),
            child: const Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '📱 ROI Selection',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 8),
                Text('• Tap to select/deselect a cell'),
                Text('• Drag from unselected cell → Select multiple cells'),
                Text('• Drag from selected cell → Deselect cells'),
                Text('• Selected cells are highlighted in red'),
              ],
            ),
          ),
          const SizedBox(height: 24),
          Text(
            'Grid Cells (${controller.cellsRows}x${controller.cellsColumns}):',
            style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 12),
          Center(
            child: Container(
              padding: const EdgeInsets.all(8.0),
              decoration: BoxDecoration(
                color: Colors.grey[100],
                border: Border.all(color: Colors.grey[300]!, width: 2),
                borderRadius: BorderRadius.circular(12),
              ),
              child: CellsWidget(
                controller: controller,
                enableSelection: true,
                borderColor: Colors.blue[300]!,
                borderWidth: 0.5,
                cellColor: Colors.white,
                selectedCellColor: Colors.red.withOpacity(0.7),
                onSelectionChanged: (indices) {
                  setState(() {
                    selectedIndices = indices;
                  });
                },
              ),
            ),
          ),
          const SizedBox(height: 20),
          CellsSelectionButtons(
            controller: controller,
            onSave: (indices) {
              setState(() {
                selectedIndices = indices;
              });
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('✅ Saved ${indices.length} cells')),
              );
            },
            onDelete: (indices) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('🗑️ Deleted ${indices.length} cells')),
              );
            },
            onClear: () {
              setState(() {
                selectedIndices = [];
              });
            },
          ),
          if (selectedIndices.isNotEmpty) ...[
            const SizedBox(height: 20),
            Container(
              padding: const EdgeInsets.all(16.0),
              decoration: BoxDecoration(
                color: Colors.green[50],
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: Colors.green[300]!, width: 2),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Selected: ${selectedIndices.length} cells',
                    style: const TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 12),
                  SelectableText(
                    '[${selectedIndices.join(",")}]',
                    style: const TextStyle(
                      fontSize: 12,
                      fontFamily: 'monospace',
                    ),
                  ),
                ],
              ),
            ),
          ],
        ],
      ),
    );
  }
}

/// Excel Table Page
class ExcelTablePage extends StatefulWidget {
  const ExcelTablePage({super.key});

  @override
  State<ExcelTablePage> createState() => _ExcelTablePageState();
}

class _ExcelTablePageState extends State<ExcelTablePage> {
  final CellsController controller = CellsController(
    screenWidth: 1200.0, // Tăng width để text có nhiều chỗ hơn
    screenHeight: 600.0,
    cellsRows: 10,
    cellsColumns: 5,
  );

  late CellsDataController dataController;
  late CellsSizeController sizeController;
  List<List<String>> tableData = [];
  String searchText = '';
  List<int> searchResults = [];
  bool showSizeInput = false;

  @override
  void initState() {
    super.initState();
    dataController = CellsDataController(controller);
    sizeController = CellsSizeController(controller);
    sizeController.enableCustomSizes = true;
    _loadSampleData();
    dataController.addListener(_onDataChanged);
    sizeController.addListener(_onDataChanged);
  }

  void _loadSampleData() {
    final data = [
      ['Name', 'Age', 'City', 'Email', 'Phone'],
      ['John', '25', 'New York', '[email protected]', '123-456-7890'],
      ['Jane', '30', 'London', '[email protected]', '098-765-4321'],
      ['Bob', '35', 'Paris', '[email protected]', '555-123-4567'],
      ['Alice', '28', 'Tokyo', '[email protected]', '111-222-3333'],
      ['Charlie', '32', 'Berlin', '[email protected]', '444-555-6666'],
    ];
    dataController.loadData(data);
    tableData = data;
  }

  void _onDataChanged() {
    setState(() {
      tableData = dataController.exportData();
    });
  }

  @override
  void dispose() {
    dataController.removeListener(_onDataChanged);
    sizeController.removeListener(_onDataChanged);
    dataController.dispose();
    sizeController.dispose();
    controller.dispose();
    super.dispose();
  }

  void _search() {
    if (searchText.isEmpty) {
      setState(() {
        searchResults = [];
      });
      return;
    }
    setState(() {
      searchResults = CellsSearch.searchCells(
        dataController,
        controller,
        searchText,
        caseSensitive: false,
      );
    });
  }

  void _sortByColumn(int columnIndex) {
    CellsSort.sortByColumn(
      dataController,
      controller,
      columnIndex,
      ascending: true,
      startRow: 1, // Skip header
    );
  }

  void _exportToCsv() {
    final csv = CellsExportImport.exportToCsv(dataController, controller);
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('CSV exported (${csv.length} characters)'),
        action: SnackBarAction(
          label: 'Copy',
          onPressed: () {
            // Copy to clipboard (would need clipboard package)
          },
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            padding: const EdgeInsets.all(16.0),
            decoration: BoxDecoration(
              color: Colors.green.withOpacity(0.1),
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: Colors.green),
            ),
            child: const Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '📊 Excel-like Table',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 8),
                Text('• Click cell to edit'),
                Text('• Search, sort, filter data'),
                Text('• Export to CSV/JSON'),
                Text('• Customize cell formatting'),
              ],
            ),
          ),
          const SizedBox(height: 24),
          // Search bar
          Row(
            children: [
              Expanded(
                child: TextField(
                  decoration: const InputDecoration(
                    labelText: 'Search',
                    hintText: 'Enter text to search',
                    prefixIcon: Icon(Icons.search),
                    border: OutlineInputBorder(),
                  ),
                  onChanged: (value) {
                    setState(() {
                      searchText = value;
                    });
                    _search();
                  },
                ),
              ),
              const SizedBox(width: 8),
              ElevatedButton(onPressed: _search, child: const Text('Search')),
            ],
          ),
          if (searchResults.isNotEmpty) ...[
            const SizedBox(height: 8),
            Text(
              'Found ${searchResults.length} results',
              style: const TextStyle(fontSize: 14, color: Colors.blue),
            ),
          ],
          const SizedBox(height: 16),
          // Toolbar
          Row(
            children: [
              ElevatedButton.icon(
                onPressed: () => _sortByColumn(0),
                icon: const Icon(Icons.sort),
                label: const Text('Sort by Name'),
              ),
              const SizedBox(width: 8),
              ElevatedButton.icon(
                onPressed: () => _sortByColumn(1),
                icon: const Icon(Icons.sort),
                label: const Text('Sort by Age'),
              ),
              const SizedBox(width: 8),
              ElevatedButton.icon(
                onPressed: _exportToCsv,
                icon: const Icon(Icons.download),
                label: const Text('Export CSV'),
              ),
              const SizedBox(width: 8),
              ElevatedButton.icon(
                onPressed: () {
                  setState(() {
                    showSizeInput = !showSizeInput;
                  });
                },
                icon: Icon(showSizeInput ? Icons.close : Icons.tune),
                label: Text(showSizeInput ? 'Hide Sizes' : 'Custom Sizes'),
              ),
            ],
          ),
          const SizedBox(height: 16),
          // Size Input Widget
          if (showSizeInput) ...[
            Container(
              padding: const EdgeInsets.all(16.0),
              decoration: BoxDecoration(
                color: Colors.orange[50],
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: Colors.orange[300]!, width: 2),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Custom Column/Row Sizes:',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 12),
                  SizedBox(
                    height: 300,
                    child: CellsSizeInputWidget(
                      controller: controller,
                      sizeController: sizeController,
                      onSizeChanged: (index, size, isColumn) {
                        setState(() {});
                      },
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 16),
          ],
          // Table
          Text(
            'Table Data (${controller.cellsRows}x${controller.cellsColumns}):',
            style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 12),
          Center(
            child: Container(
              padding: const EdgeInsets.all(8.0),
              decoration: BoxDecoration(
                color: Colors.grey[100],
                border: Border.all(color: Colors.grey[300]!, width: 2),
                borderRadius: BorderRadius.circular(12),
              ),
              child: CellsTableWidget(
                controller: controller,
                dataController: dataController,
                sizeController: sizeController,
                enableEdit: true,
                showHeader: true,
                headerBackgroundColor: Colors.blue[100],
                borderColor: Colors.grey[400],
                borderWidth: 1.0,
                onDataChanged: (data) {
                  setState(() {
                    tableData = data;
                  });
                },
              ),
            ),
          ),
          const SizedBox(height: 20),
          // Data info
          Container(
            padding: const EdgeInsets.all(16.0),
            decoration: BoxDecoration(
              color: Colors.blue[50],
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: Colors.blue[300]!, width: 2),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  'Data Info:',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 12),
                Text('Rows: ${tableData.length}'),
                Text(
                  'Columns: ${tableData.isNotEmpty ? tableData[0].length : 0}',
                ),
                const SizedBox(height: 12),
                const Text(
                  'Sample Data:',
                  style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                if (tableData.isNotEmpty)
                  ...tableData.take(3).map((row) => Text(row.join(' | '))),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
12
likes
0
points
22
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter package for creating grid cells with customizable screen size and number of cells for ROI camera applications, data tables, Excel-like spreadsheets, and more!

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on custom_roi_cells