import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class DynamicTable extends StatefulWidget { final List headers; final String? tableName; final List> data; final BoxDecoration? headerDecoration; final BoxDecoration? cellDecoration; final Size size; final bool withCheckBox; final bool withSelectAll; final bool isEmpty; final void Function(bool?)? selectAll; final void Function(int, bool, dynamic)? onRowSelected; final List? initialSelectedIds; final int uuidIndex; final Function(dynamic selectedRows)? onSelectionChanged; final Function(int rowIndex)? onSettingsPressed; const DynamicTable({ super.key, required this.headers, required this.data, required this.size, this.tableName, required this.isEmpty, required this.withCheckBox, required this.withSelectAll, this.headerDecoration, this.cellDecoration, this.selectAll, this.onRowSelected, this.initialSelectedIds, required this.uuidIndex, this.onSelectionChanged, this.onSettingsPressed, }); @override _DynamicTableState createState() => _DynamicTableState(); } class _DynamicTableState extends State { late List _selectedRows; bool _selectAll = false; final ScrollController _verticalScrollController = ScrollController(); final ScrollController _horizontalScrollController = ScrollController(); static const double _fixedRowHeight = 60; static const double _checkboxColumnWidth = 50; static const double _settingsColumnWidth = 100; @override void initState() { super.initState(); _initializeSelection(); } @override void didUpdateWidget(DynamicTable oldWidget) { super.didUpdateWidget(oldWidget); if (!_compareListOfLists(oldWidget.data, widget.data)) { _initializeSelection(); } } bool _compareListOfLists( List> oldList, List> newList) { if (oldList.length != newList.length) return false; for (int i = 0; i < oldList.length; i++) { if (oldList[i].length != newList[i].length) return false; for (int j = 0; j < oldList[i].length; j++) { if (oldList[i][j] != newList[i][j]) return false; } } return true; } void _initializeSelection() { _selectedRows = List.filled(widget.data.length, false); _selectAll = false; } void _toggleRowSelection(int index) { setState(() { _selectedRows[index] = !_selectedRows[index]; _selectAll = _selectedRows.every((isSelected) => isSelected); }); widget.onSelectionChanged?.call(_selectedRows); context.read().add(UpdateSelection(_selectedRows)); } void _toggleSelectAll(bool? value) { setState(() { _selectAll = value ?? false; _selectedRows = List.filled(widget.data.length, _selectAll); }); widget.onSelectionChanged?.call(_selectedRows); context.read().add(UpdateSelection(_selectedRows)); } double get _totalTableWidth { final hasSettings = widget.headers.contains('Settings'); final base = (widget.withCheckBox ? _checkboxColumnWidth : 0) + (hasSettings ? _settingsColumnWidth : 0); final regularCount = widget.headers.length - (hasSettings ? 1 : 0); final regularWidth = (widget.size.width - base) / regularCount; return base + regularCount * regularWidth; } @override Widget build(BuildContext context) { return Container( width: widget.size.width, height: widget.size.height, decoration: widget.cellDecoration, child: ScrollConfiguration( behavior: const ScrollBehavior().copyWith(scrollbars: false), child: Scrollbar( controller: _horizontalScrollController, thumbVisibility: true, trackVisibility: true, notificationPredicate: (notif) => notif.metrics.axis == Axis.horizontal, child: SingleChildScrollView( controller: _horizontalScrollController, scrollDirection: Axis.horizontal, child: SizedBox( width: _totalTableWidth, child: Column( children: [ Container( height: _fixedRowHeight, decoration: widget.headerDecoration ?? const BoxDecoration(color: ColorsManager.boxColor), child: Row( children: [ if (widget.withCheckBox) _buildSelectAllCheckbox(_checkboxColumnWidth), for (var i = 0; i < widget.headers.length; i++) _buildTableHeaderCell( widget.headers[i], widget.headers[i] == 'Settings' ? _settingsColumnWidth : (_totalTableWidth - (widget.withCheckBox ? _checkboxColumnWidth : 0) - (widget.headers.contains('Settings') ? _settingsColumnWidth : 0)) / (widget.headers.length - (widget.headers.contains('Settings') ? 1 : 0)), ), ], ), ), Expanded( child: widget.isEmpty ? _buildEmptyState() : Scrollbar( controller: _verticalScrollController, thumbVisibility: true, trackVisibility: true, notificationPredicate: (notif) => notif.metrics.axis == Axis.vertical, child: ListView.builder( controller: _verticalScrollController, itemCount: widget.data.length, itemBuilder: (_, rowIndex) { final row = widget.data[rowIndex]; return SizedBox( height: _fixedRowHeight, child: Row( children: [ if (widget.withCheckBox) _buildRowCheckbox( rowIndex, _checkboxColumnWidth, ), for (var colIndex = 0; colIndex < row.length; colIndex++) widget.headers[colIndex] == 'Settings' ? buildSettingsIcon( width: _settingsColumnWidth, onTap: () => widget .onSettingsPressed ?.call(rowIndex), ) : _buildTableCell( row[colIndex].toString(), width: widget.headers[ colIndex] == 'Settings' ? _settingsColumnWidth : (_totalTableWidth - (widget.withCheckBox ? _checkboxColumnWidth : 0) - (widget.headers .contains( 'Settings') ? _settingsColumnWidth : 0)) / (widget.headers.length - (widget.headers .contains( 'Settings') ? 1 : 0)), rowIndex: rowIndex, columnIndex: colIndex, ), ], ), ); }, ), ), ), ], ), ), ), ), ), ); } Widget _buildEmptyState() => Container( height: widget.size.height, color: ColorsManager.whiteColors, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Column( children: [ SvgPicture.asset(Assets.emptyTable), const SizedBox(height: 15), Text( widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices', style: Theme.of(context) .textTheme .bodySmall! .copyWith(color: ColorsManager.grayColor), ) ], ), ], ), SizedBox(height: widget.size.height * 0.5), ], ), ); Widget _buildSelectAllCheckbox(double width) { return Container( width: width, decoration: const BoxDecoration( border: Border.symmetric( vertical: BorderSide(color: ColorsManager.boxDivider), ), ), child: Checkbox( value: _selectAll, onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null, ), ); } Widget _buildRowCheckbox(int index, double width) { return Container( width: width, padding: const EdgeInsets.all(8.0), height: _fixedRowHeight, decoration: const BoxDecoration( border: Border( bottom: BorderSide( color: ColorsManager.boxDivider, width: 1.0, ), ), color: ColorsManager.whiteColors, ), alignment: Alignment.centerLeft, child: Center( child: Checkbox( value: _selectedRows[index], onChanged: (bool? value) { _toggleRowSelection(index); }, ), ), ); } Widget _buildTableHeaderCell(String title, double width) { return Container( width: width, decoration: const BoxDecoration( border: Border.symmetric( vertical: BorderSide(color: ColorsManager.boxDivider), ), ), constraints: BoxConstraints(minHeight: 40, maxHeight: _fixedRowHeight), alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), child: Text( title, style: context.textTheme.titleSmall!.copyWith( color: ColorsManager.grayColor, fontSize: 12, fontWeight: FontWeight.w400, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ); } Widget _buildTableCell(String content, {required double width, required int rowIndex, required int columnIndex}) { bool isBatteryLevel = content.endsWith('%'); double? batteryLevel; if (isBatteryLevel) { batteryLevel = double.tryParse(content.replaceAll('%', '').trim()); } bool isSettingsColumn = widget.headers[columnIndex] == 'Settings'; if (isSettingsColumn) { return buildSettingsIcon( width: width, onTap: () => widget.onSettingsPressed?.call(rowIndex)); } Color? statusColor; switch (content) { case 'Effective': statusColor = ColorsManager.textGreen; break; case 'Expired': statusColor = ColorsManager.red; break; case 'To be effective': statusColor = ColorsManager.yaGreen; break; case 'Online': statusColor = ColorsManager.green; break; case 'Offline': statusColor = ColorsManager.red; break; default: statusColor = Colors.black; } return Container( width: width, height: _fixedRowHeight, padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), decoration: const BoxDecoration( border: Border( bottom: BorderSide( color: ColorsManager.boxDivider, width: 1.0, ), ), color: Colors.white, ), alignment: Alignment.centerLeft, child: Text( content, style: TextStyle( color: (batteryLevel != null && batteryLevel < 20) ? ColorsManager.red : (batteryLevel != null && batteryLevel > 20) ? ColorsManager.green : statusColor, fontSize: 13, fontWeight: FontWeight.w400, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ); } Widget buildSettingsIcon({required double width, VoidCallback? onTap}) { return Container( width: width, height: _fixedRowHeight, padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10), decoration: const BoxDecoration( color: ColorsManager.whiteColors, border: Border( bottom: BorderSide( color: ColorsManager.boxDivider, width: 1.0, ), ), ), child: Align( alignment: Alignment.centerLeft, child: Container( width: 50, decoration: BoxDecoration( color: const Color(0xFFF7F8FA), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.17), blurRadius: 14, offset: const Offset(0, 4), ), ], ), child: InkWell( onTap: onTap, child: Padding( padding: EdgeInsets.all(8.0), child: Center( child: SvgPicture.asset( Assets.settings, width: 40, height: 20, color: ColorsManager.primaryColor, ), ), ), ), ), ), ); } }