This commit is contained in:
Phuoc Nguyen
2025-10-10 16:38:07 +07:00
parent e5b247d622
commit b94c158004
177 changed files with 25080 additions and 152 deletions

View File

@@ -0,0 +1,358 @@
/// Database performance optimization utilities for Hive CE
///
/// Features:
/// - Lazy box loading for large datasets
/// - Database compaction strategies
/// - Query optimization helpers
/// - Cache management
/// - Batch operations
import 'package:hive_ce/hive.dart';
import '../constants/performance_constants.dart';
import 'performance_monitor.dart';
/// Database optimization helpers for Hive CE
class DatabaseOptimizer {
/// Batch write operations for better performance
static Future<void> batchWrite<T>({
required Box<T> box,
required Map<String, T> items,
}) async {
final startTime = DateTime.now();
// Hive doesn't support batch operations natively,
// but we can optimize by reducing individual writes
final entries = items.entries.toList();
final batchSize = PerformanceConstants.databaseBatchSize;
for (var i = 0; i < entries.length; i += batchSize) {
final end = (i + batchSize < entries.length)
? i + batchSize
: entries.length;
final batch = entries.sublist(i, end);
for (final entry in batch) {
await box.put(entry.key, entry.value);
}
}
final duration = DateTime.now().difference(startTime);
DatabaseTracker.logQuery(
operation: 'batchWrite',
duration: duration,
affectedRows: items.length,
);
}
/// Batch delete operations
static Future<void> batchDelete<T>({
required Box<T> box,
required List<String> keys,
}) async {
final startTime = DateTime.now();
final batchSize = PerformanceConstants.databaseBatchSize;
for (var i = 0; i < keys.length; i += batchSize) {
final end = (i + batchSize < keys.length) ? i + batchSize : keys.length;
final batch = keys.sublist(i, end);
for (final key in batch) {
await box.delete(key);
}
}
final duration = DateTime.now().difference(startTime);
DatabaseTracker.logQuery(
operation: 'batchDelete',
duration: duration,
affectedRows: keys.length,
);
}
/// Compact database to reduce file size
static Future<void> compactBox<T>(Box<T> box) async {
final startTime = DateTime.now();
await box.compact();
final duration = DateTime.now().difference(startTime);
DatabaseTracker.logQuery(
operation: 'compact',
duration: duration,
);
}
/// Efficient filtered query with caching
static List<T> queryWithFilter<T>({
required Box<T> box,
required bool Function(T item) filter,
int? limit,
}) {
final startTime = DateTime.now();
final results = <T>[];
final values = box.values;
for (final item in values) {
if (filter(item)) {
results.add(item);
if (limit != null && results.length >= limit) {
break;
}
}
}
final duration = DateTime.now().difference(startTime);
DatabaseTracker.logQuery(
operation: 'queryWithFilter',
duration: duration,
affectedRows: results.length,
);
return results;
}
/// Efficient pagination
static List<T> queryWithPagination<T>({
required Box<T> box,
required int page,
int pageSize = 20,
bool Function(T item)? filter,
}) {
final startTime = DateTime.now();
final skip = page * pageSize;
final results = <T>[];
var skipped = 0;
var taken = 0;
final values = box.values;
for (final item in values) {
if (filter != null && !filter(item)) {
continue;
}
if (skipped < skip) {
skipped++;
continue;
}
if (taken < pageSize) {
results.add(item);
taken++;
} else {
break;
}
}
final duration = DateTime.now().difference(startTime);
DatabaseTracker.logQuery(
operation: 'queryWithPagination',
duration: duration,
affectedRows: results.length,
);
return results;
}
/// Check if box needs compaction
static bool needsCompaction<T>(Box<T> box) {
// Hive automatically compacts when needed
// This is a placeholder for custom compaction logic
return false;
}
/// Get box statistics
static Map<String, dynamic> getBoxStats<T>(Box<T> box) {
return {
'name': box.name,
'length': box.length,
'isEmpty': box.isEmpty,
'isOpen': box.isOpen,
};
}
/// Clear old cache entries based on timestamp
static Future<void> clearOldEntries<T>({
required Box<T> box,
required DateTime Function(T item) getTimestamp,
required Duration maxAge,
}) async {
final startTime = DateTime.now();
final now = DateTime.now();
final keysToDelete = <String>[];
for (final key in box.keys) {
final item = box.get(key);
if (item != null) {
final timestamp = getTimestamp(item);
if (now.difference(timestamp) > maxAge) {
keysToDelete.add(key.toString());
}
}
}
await batchDelete(box: box, keys: keysToDelete);
final duration = DateTime.now().difference(startTime);
DatabaseTracker.logQuery(
operation: 'clearOldEntries',
duration: duration,
affectedRows: keysToDelete.length,
);
}
/// Optimize box by removing duplicates (if applicable)
static Future<void> removeDuplicates<T>({
required Box<T> box,
required String Function(T item) getUniqueId,
}) async {
final startTime = DateTime.now();
final seen = <String>{};
final keysToDelete = <String>[];
for (final key in box.keys) {
final item = box.get(key);
if (item != null) {
final uniqueId = getUniqueId(item);
if (seen.contains(uniqueId)) {
keysToDelete.add(key.toString());
} else {
seen.add(uniqueId);
}
}
}
await batchDelete(box: box, keys: keysToDelete);
final duration = DateTime.now().difference(startTime);
DatabaseTracker.logQuery(
operation: 'removeDuplicates',
duration: duration,
affectedRows: keysToDelete.length,
);
}
}
/// Lazy box helper for large datasets
class LazyBoxHelper {
/// Load items in chunks to avoid memory issues
static Future<List<T>> loadInChunks<T>({
required LazyBox<T> lazyBox,
int chunkSize = 50,
bool Function(T item)? filter,
}) async {
final startTime = DateTime.now();
final results = <T>[];
final keys = lazyBox.keys.toList();
for (var i = 0; i < keys.length; i += chunkSize) {
final end = (i + chunkSize < keys.length) ? i + chunkSize : keys.length;
final chunkKeys = keys.sublist(i, end);
for (final key in chunkKeys) {
final item = await lazyBox.get(key);
if (item != null) {
if (filter == null || filter(item)) {
results.add(item);
}
}
}
}
final duration = DateTime.now().difference(startTime);
DatabaseTracker.logQuery(
operation: 'loadInChunks',
duration: duration,
affectedRows: results.length,
);
return results;
}
/// Get paginated items from lazy box
static Future<List<T>> getPaginated<T>({
required LazyBox<T> lazyBox,
required int page,
int pageSize = 20,
}) async {
final startTime = DateTime.now();
final skip = page * pageSize;
final keys = lazyBox.keys.skip(skip).take(pageSize).toList();
final results = <T>[];
for (final key in keys) {
final item = await lazyBox.get(key);
if (item != null) {
results.add(item);
}
}
final duration = DateTime.now().difference(startTime);
DatabaseTracker.logQuery(
operation: 'getPaginated',
duration: duration,
affectedRows: results.length,
);
return results;
}
}
/// Cache manager for database queries
class QueryCache<T> {
final Map<String, _CachedQuery<T>> _cache = {};
final Duration cacheDuration;
QueryCache({this.cacheDuration = const Duration(minutes: 5)});
/// Get or compute cached result
Future<T> getOrCompute(
String key,
Future<T> Function() compute,
) async {
final cached = _cache[key];
final now = DateTime.now();
if (cached != null && now.difference(cached.timestamp) < cacheDuration) {
return cached.value;
}
final value = await compute();
_cache[key] = _CachedQuery(value: value, timestamp: now);
// Clean old cache entries
_cleanCache();
return value;
}
/// Invalidate specific cache entry
void invalidate(String key) {
_cache.remove(key);
}
/// Clear all cache
void clear() {
_cache.clear();
}
void _cleanCache() {
final now = DateTime.now();
_cache.removeWhere((key, value) {
return now.difference(value.timestamp) > cacheDuration;
});
}
}
class _CachedQuery<T> {
final T value;
final DateTime timestamp;
_CachedQuery({
required this.value,
required this.timestamp,
});
}

View File

@@ -0,0 +1,102 @@
/// Performance utility for debouncing rapid function calls
///
/// Use cases:
/// - Search input (300ms delay before search)
/// - Auto-save functionality
/// - API request rate limiting
/// - Scroll position updates
import 'dart:async';
import 'package:flutter/foundation.dart';
/// Debouncer utility to prevent excessive function calls
///
/// Example usage:
/// ```dart
/// final searchDebouncer = Debouncer(milliseconds: 300);
///
/// void onSearchChanged(String query) {
/// searchDebouncer.run(() {
/// performSearch(query);
/// });
/// }
/// ```
class Debouncer {
final int milliseconds;
Timer? _timer;
Debouncer({required this.milliseconds});
/// Run the action after the debounce delay
void run(VoidCallback action) {
_timer?.cancel();
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
/// Cancel any pending debounced action
void cancel() {
_timer?.cancel();
}
/// Dispose of the debouncer
void dispose() {
_timer?.cancel();
}
}
/// Throttler utility to limit function call frequency
///
/// Example usage:
/// ```dart
/// final scrollThrottler = Throttler(milliseconds: 100);
///
/// void onScroll() {
/// scrollThrottler.run(() {
/// updateScrollPosition();
/// });
/// }
/// ```
class Throttler {
final int milliseconds;
Timer? _timer;
bool _isReady = true;
Throttler({required this.milliseconds});
/// Run the action only if throttle period has passed
void run(VoidCallback action) {
if (_isReady) {
_isReady = false;
action();
_timer = Timer(Duration(milliseconds: milliseconds), () {
_isReady = true;
});
}
}
/// Cancel throttler
void cancel() {
_timer?.cancel();
_isReady = true;
}
/// Dispose of the throttler
void dispose() {
_timer?.cancel();
}
}
/// Search-specific debouncer with common configuration
class SearchDebouncer extends Debouncer {
SearchDebouncer() : super(milliseconds: 300);
}
/// Auto-save debouncer with longer delay
class AutoSaveDebouncer extends Debouncer {
AutoSaveDebouncer() : super(milliseconds: 1000);
}
/// Scroll throttler for performance
class ScrollThrottler extends Throttler {
ScrollThrottler() : super(milliseconds: 100);
}

View File

@@ -0,0 +1,76 @@
extension StringExtension on String {
/// Capitalize first letter
String capitalize() {
if (isEmpty) return this;
return '${this[0].toUpperCase()}${substring(1)}';
}
/// Check if string is a valid number
bool isNumeric() {
return double.tryParse(this) != null;
}
/// Truncate string with ellipsis
String truncate(int maxLength, {String suffix = '...'}) {
if (length <= maxLength) return this;
return '${substring(0, maxLength)}$suffix';
}
}
extension DateTimeExtension on DateTime {
/// Check if date is today
bool isToday() {
final now = DateTime.now();
return year == now.year && month == now.month && day == now.day;
}
/// Check if date is yesterday
bool isYesterday() {
final yesterday = DateTime.now().subtract(const Duration(days: 1));
return year == yesterday.year &&
month == yesterday.month &&
day == yesterday.day;
}
/// Get relative time string (e.g., "2 hours ago")
String getRelativeTime() {
final now = DateTime.now();
final difference = now.difference(this);
if (difference.inSeconds < 60) {
return 'Just now';
} else if (difference.inMinutes < 60) {
return '${difference.inMinutes}m ago';
} else if (difference.inHours < 24) {
return '${difference.inHours}h ago';
} else if (difference.inDays < 7) {
return '${difference.inDays}d ago';
} else {
return '${(difference.inDays / 7).floor()}w ago';
}
}
}
extension DoubleExtension on double {
/// Round to specific decimal places
double roundToDecimals(int decimals) {
final mod = 10.0 * decimals;
return (this * mod).round() / mod;
}
/// Format as currency
String toCurrency({String symbol = '\$'}) {
return '$symbol${toStringAsFixed(2)}';
}
}
extension ListExtension<T> on List<T> {
/// Check if list is not null and not empty
bool get isNotEmpty => this.isNotEmpty;
/// Get first element or null
T? get firstOrNull => isEmpty ? null : first;
/// Get last element or null
T? get lastOrNull => isEmpty ? null : last;
}

View File

@@ -0,0 +1,43 @@
import 'package:intl/intl.dart';
/// Utility class for formatting values
class Formatters {
Formatters._();
/// Format price with currency symbol
static String formatPrice(double price, {String currency = 'USD'}) {
final formatter = NumberFormat.currency(symbol: '\$', decimalDigits: 2);
return formatter.format(price);
}
/// Format date
static String formatDate(DateTime date) {
return DateFormat('MMM dd, yyyy').format(date);
}
/// Format date and time
static String formatDateTime(DateTime dateTime) {
return DateFormat('MMM dd, yyyy hh:mm a').format(dateTime);
}
/// Format time only
static String formatTime(DateTime time) {
return DateFormat('hh:mm a').format(time);
}
/// Format number with thousand separators
static String formatNumber(int number) {
final formatter = NumberFormat('#,###');
return formatter.format(number);
}
/// Format percentage
static String formatPercentage(double value, {int decimals = 0}) {
return '${value.toStringAsFixed(decimals)}%';
}
/// Format quantity (e.g., "5 items")
static String formatQuantity(int quantity) {
return '$quantity ${quantity == 1 ? 'item' : 'items'}';
}
}

View File

@@ -0,0 +1,303 @@
/// Performance monitoring utilities
///
/// Track and monitor app performance:
/// - Frame rendering times
/// - Memory usage
/// - Widget rebuild counts
/// - Network request durations
/// - Database query times
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../constants/performance_constants.dart';
/// Performance monitor for tracking app performance metrics
class PerformanceMonitor {
static final PerformanceMonitor _instance = PerformanceMonitor._internal();
factory PerformanceMonitor() => _instance;
PerformanceMonitor._internal();
final Map<String, _PerformanceMetric> _metrics = {};
final List<_PerformanceLog> _logs = [];
/// Start tracking a performance metric
void startTracking(String name) {
if (kDebugMode) {
_metrics[name] = _PerformanceMetric(
name: name,
startTime: DateTime.now(),
);
developer.Timeline.startSync(name);
}
}
/// Stop tracking and log the metric
void stopTracking(String name) {
if (kDebugMode) {
final metric = _metrics[name];
if (metric != null) {
final duration = DateTime.now().difference(metric.startTime);
_logMetric(name, duration);
_metrics.remove(name);
developer.Timeline.finishSync();
// Warn if operation took too long
if (duration.inMilliseconds > PerformanceConstants.longFrameThresholdMs) {
debugPrint(
'⚠️ PERFORMANCE WARNING: $name took ${duration.inMilliseconds}ms',
);
}
}
}
}
/// Track a function execution time
Future<T> trackAsync<T>(String name, Future<T> Function() function) async {
startTracking(name);
try {
return await function();
} finally {
stopTracking(name);
}
}
/// Track a synchronous function execution time
T track<T>(String name, T Function() function) {
startTracking(name);
try {
return function();
} finally {
stopTracking(name);
}
}
/// Log a custom metric
void logMetric(String name, Duration duration, {Map<String, dynamic>? metadata}) {
if (kDebugMode) {
_logMetric(name, duration, metadata: metadata);
}
}
void _logMetric(String name, Duration duration, {Map<String, dynamic>? metadata}) {
final log = _PerformanceLog(
name: name,
duration: duration,
timestamp: DateTime.now(),
metadata: metadata,
);
_logs.add(log);
// Keep only last 100 logs
if (_logs.length > 100) {
_logs.removeAt(0);
}
debugPrint('📊 PERFORMANCE: $name - ${duration.inMilliseconds}ms');
}
/// Get performance summary
Map<String, dynamic> getSummary() {
if (_logs.isEmpty) return {};
final summary = <String, List<int>>{};
for (final log in _logs) {
summary.putIfAbsent(log.name, () => []).add(log.duration.inMilliseconds);
}
return summary.map((key, values) {
final avg = values.reduce((a, b) => a + b) / values.length;
final max = values.reduce((a, b) => a > b ? a : b);
final min = values.reduce((a, b) => a < b ? a : b);
return MapEntry(key, {
'average': avg.toStringAsFixed(2),
'max': max,
'min': min,
'count': values.length,
});
});
}
/// Clear all logs
void clearLogs() {
_logs.clear();
}
/// Print performance summary
void printSummary() {
if (kDebugMode) {
final summary = getSummary();
debugPrint('=== PERFORMANCE SUMMARY ===');
summary.forEach((key, value) {
debugPrint('$key: $value');
});
debugPrint('=========================');
}
}
}
class _PerformanceMetric {
final String name;
final DateTime startTime;
_PerformanceMetric({
required this.name,
required this.startTime,
});
}
class _PerformanceLog {
final String name;
final Duration duration;
final DateTime timestamp;
final Map<String, dynamic>? metadata;
_PerformanceLog({
required this.name,
required this.duration,
required this.timestamp,
this.metadata,
});
}
/// Widget to track rebuild count
class RebuildTracker extends StatelessWidget {
final Widget child;
final String name;
const RebuildTracker({
super.key,
required this.child,
required this.name,
});
static final Map<String, int> _rebuildCounts = {};
@override
Widget build(BuildContext context) {
if (kDebugMode) {
_rebuildCounts[name] = (_rebuildCounts[name] ?? 0) + 1;
debugPrint('🔄 REBUILD: $name (${_rebuildCounts[name]} times)');
}
return child;
}
static void printRebuildStats() {
if (kDebugMode) {
debugPrint('=== REBUILD STATS ===');
_rebuildCounts.forEach((key, value) {
debugPrint('$key: $value rebuilds');
});
debugPrint('====================');
}
}
static void clearStats() {
_rebuildCounts.clear();
}
}
/// Memory usage tracker (simplified)
class MemoryTracker {
static void logMemoryUsage(String label) {
if (kDebugMode) {
// Note: Actual memory tracking would require platform-specific implementation
debugPrint('💾 MEMORY CHECK: $label');
}
}
}
/// Network request tracker
class NetworkTracker {
static final List<_NetworkLog> _logs = [];
static void logRequest({
required String url,
required Duration duration,
required int statusCode,
int? responseSize,
}) {
if (kDebugMode) {
final log = _NetworkLog(
url: url,
duration: duration,
statusCode: statusCode,
responseSize: responseSize,
timestamp: DateTime.now(),
);
_logs.add(log);
// Keep only last 50 logs
if (_logs.length > 50) {
_logs.removeAt(0);
}
debugPrint(
'🌐 NETWORK: $url - ${duration.inMilliseconds}ms (${statusCode})',
);
}
}
static void printStats() {
if (kDebugMode && _logs.isNotEmpty) {
final totalDuration = _logs.fold<int>(
0,
(sum, log) => sum + log.duration.inMilliseconds,
);
final avgDuration = totalDuration / _logs.length;
debugPrint('=== NETWORK STATS ===');
debugPrint('Total requests: ${_logs.length}');
debugPrint('Average duration: ${avgDuration.toStringAsFixed(2)}ms');
debugPrint('====================');
}
}
static void clearLogs() {
_logs.clear();
}
}
class _NetworkLog {
final String url;
final Duration duration;
final int statusCode;
final int? responseSize;
final DateTime timestamp;
_NetworkLog({
required this.url,
required this.duration,
required this.statusCode,
this.responseSize,
required this.timestamp,
});
}
/// Database query tracker
class DatabaseTracker {
static void logQuery({
required String operation,
required Duration duration,
int? affectedRows,
}) {
if (kDebugMode) {
debugPrint(
'💿 DATABASE: $operation - ${duration.inMilliseconds}ms'
'${affectedRows != null ? ' ($affectedRows rows)' : ''}',
);
if (duration.inMilliseconds > 100) {
debugPrint('⚠️ SLOW QUERY: $operation took ${duration.inMilliseconds}ms');
}
}
}
}
/// Extension for easy performance tracking
extension PerformanceTrackingExtension<T> on Future<T> {
Future<T> trackPerformance(String name) {
return PerformanceMonitor().trackAsync(name, () => this);
}
}

View File

@@ -0,0 +1,274 @@
/// Responsive layout utilities for optimal performance across devices
///
/// Features:
/// - Breakpoint-based layouts
/// - Adaptive grid columns
/// - Performance-optimized responsive widgets
/// - Device-specific optimizations
import 'package:flutter/material.dart';
import '../constants/performance_constants.dart';
/// Responsive helper for device-specific optimizations
class ResponsiveHelper {
/// Check if device is mobile
static bool isMobile(BuildContext context) {
return MediaQuery.of(context).size.width < PerformanceConstants.mobileBreakpoint;
}
/// Check if device is tablet
static bool isTablet(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width >= PerformanceConstants.mobileBreakpoint &&
width < PerformanceConstants.desktopBreakpoint;
}
/// Check if device is desktop
static bool isDesktop(BuildContext context) {
return MediaQuery.of(context).size.width >= PerformanceConstants.desktopBreakpoint;
}
/// Get appropriate grid column count
static int getGridColumns(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return PerformanceConstants.getGridColumnCount(width);
}
/// Get appropriate cache extent
static double getCacheExtent(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return PerformanceConstants.getCacheExtent(height);
}
/// Check if high performance mode should be enabled
static bool shouldUseHighPerformance(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return PerformanceConstants.shouldUseHighPerformanceMode(width);
}
/// Get value based on screen size
static T getValue<T>(
BuildContext context, {
required T mobile,
T? tablet,
T? desktop,
}) {
if (isDesktop(context) && desktop != null) return desktop;
if (isTablet(context) && tablet != null) return tablet;
return mobile;
}
/// Get responsive padding
static EdgeInsets getResponsivePadding(BuildContext context) {
if (isDesktop(context)) {
return const EdgeInsets.all(24);
} else if (isTablet(context)) {
return const EdgeInsets.all(16);
} else {
return const EdgeInsets.all(12);
}
}
/// Get responsive spacing
static double getSpacing(BuildContext context) {
if (isDesktop(context)) return 16;
if (isTablet(context)) return 12;
return 8;
}
}
/// Responsive layout builder with performance optimization
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget? tablet;
final Widget? desktop;
const ResponsiveLayout({
super.key,
required this.mobile,
this.tablet,
this.desktop,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= PerformanceConstants.desktopBreakpoint) {
return desktop ?? tablet ?? mobile;
} else if (constraints.maxWidth >= PerformanceConstants.mobileBreakpoint) {
return tablet ?? mobile;
} else {
return mobile;
}
},
);
}
}
/// Responsive value builder
class ResponsiveValue<T> extends StatelessWidget {
final T mobile;
final T? tablet;
final T? desktop;
final Widget Function(BuildContext context, T value) builder;
const ResponsiveValue({
super.key,
required this.mobile,
this.tablet,
this.desktop,
required this.builder,
});
@override
Widget build(BuildContext context) {
final value = ResponsiveHelper.getValue(
context,
mobile: mobile,
tablet: tablet,
desktop: desktop,
);
return builder(context, value);
}
}
/// Adaptive grid configuration
class AdaptiveGridConfig {
final int crossAxisCount;
final double childAspectRatio;
final double spacing;
final double cacheExtent;
const AdaptiveGridConfig({
required this.crossAxisCount,
required this.childAspectRatio,
required this.spacing,
required this.cacheExtent,
});
factory AdaptiveGridConfig.fromContext(
BuildContext context, {
GridType type = GridType.products,
}) {
final width = MediaQuery.of(context).size.width;
final height = MediaQuery.of(context).size.height;
return AdaptiveGridConfig(
crossAxisCount: PerformanceConstants.getGridColumnCount(width),
childAspectRatio: type == GridType.products
? PerformanceConstants.productCardAspectRatio
: PerformanceConstants.categoryCardAspectRatio,
spacing: PerformanceConstants.gridSpacing,
cacheExtent: PerformanceConstants.getCacheExtent(height),
);
}
}
enum GridType {
products,
categories,
}
/// Responsive grid view that adapts to screen size
class AdaptiveGridView<T> extends StatelessWidget {
final List<T> items;
final Widget Function(BuildContext context, T item, int index) itemBuilder;
final GridType type;
final ScrollController? scrollController;
final EdgeInsets? padding;
const AdaptiveGridView({
super.key,
required this.items,
required this.itemBuilder,
this.type = GridType.products,
this.scrollController,
this.padding,
});
@override
Widget build(BuildContext context) {
final config = AdaptiveGridConfig.fromContext(context, type: type);
return GridView.builder(
controller: scrollController,
padding: padding ?? ResponsiveHelper.getResponsivePadding(context),
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
cacheExtent: config.cacheExtent,
itemCount: items.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: config.crossAxisCount,
crossAxisSpacing: config.spacing,
mainAxisSpacing: config.spacing,
childAspectRatio: config.childAspectRatio,
),
itemBuilder: (context, index) {
final item = items[index];
return RepaintBoundary(
key: ValueKey('adaptive_grid_item_$index'),
child: itemBuilder(context, item, index),
);
},
);
}
}
/// Responsive container with adaptive sizing
class ResponsiveContainer extends StatelessWidget {
final Widget child;
final double? mobileWidth;
final double? tabletWidth;
final double? desktopWidth;
const ResponsiveContainer({
super.key,
required this.child,
this.mobileWidth,
this.tabletWidth,
this.desktopWidth,
});
@override
Widget build(BuildContext context) {
final width = ResponsiveHelper.getValue(
context,
mobile: mobileWidth,
tablet: tabletWidth,
desktop: desktopWidth,
);
return Container(
width: width,
child: child,
);
}
}
/// Extension for easier responsive values
extension ResponsiveContextExtension on BuildContext {
bool get isMobile => ResponsiveHelper.isMobile(this);
bool get isTablet => ResponsiveHelper.isTablet(this);
bool get isDesktop => ResponsiveHelper.isDesktop(this);
int get gridColumns => ResponsiveHelper.getGridColumns(this);
double get cacheExtent => ResponsiveHelper.getCacheExtent(this);
double get spacing => ResponsiveHelper.getSpacing(this);
EdgeInsets get responsivePadding => ResponsiveHelper.getResponsivePadding(this);
T responsive<T>({
required T mobile,
T? tablet,
T? desktop,
}) {
return ResponsiveHelper.getValue(
this,
mobile: mobile,
tablet: tablet,
desktop: desktop,
);
}
}

View File

@@ -0,0 +1,66 @@
/// Utility class for input validation
class Validators {
Validators._();
/// Validate email
static String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return 'Enter a valid email';
}
return null;
}
/// Validate required field
static String? validateRequired(String? value, {String? fieldName}) {
if (value == null || value.isEmpty) {
return '${fieldName ?? 'This field'} is required';
}
return null;
}
/// Validate price
static String? validatePrice(String? value) {
if (value == null || value.isEmpty) {
return 'Price is required';
}
final price = double.tryParse(value);
if (price == null) {
return 'Enter a valid price';
}
if (price <= 0) {
return 'Price must be greater than 0';
}
return null;
}
/// Validate quantity
static String? validateQuantity(String? value) {
if (value == null || value.isEmpty) {
return 'Quantity is required';
}
final quantity = int.tryParse(value);
if (quantity == null) {
return 'Enter a valid quantity';
}
if (quantity < 0) {
return 'Quantity cannot be negative';
}
return null;
}
/// Validate phone number
static String? validatePhone(String? value) {
if (value == null || value.isEmpty) {
return 'Phone number is required';
}
final phoneRegex = RegExp(r'^\+?[\d\s-]{10,}$');
if (!phoneRegex.hasMatch(value)) {
return 'Enter a valid phone number';
}
return null;
}
}