baseproject

This commit is contained in:
Gaurav Kumar
2025-09-06 19:21:52 +05:30
commit 01be9df2ed
254 changed files with 28342 additions and 0 deletions

View File

@@ -0,0 +1,357 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import '../../../core/constants/ui_constants.dart';
import '../buttons/modern_button.dart';
class ModernImagePicker extends StatefulWidget {
final String? label;
final String? hint;
final Uint8List? imageBytes;
final File? imageFile;
final String? imageUrl;
final VoidCallback? onPickImage;
final VoidCallback? onRemoveImage;
final bool isLoading;
final double? height;
final double? width;
final BorderRadius? borderRadius;
final String? errorText;
final bool showRemoveButton;
final IconData? placeholderIcon;
final String? placeholderText;
const ModernImagePicker({
super.key,
this.label,
this.hint,
this.imageBytes,
this.imageFile,
this.imageUrl,
this.onPickImage,
this.onRemoveImage,
this.isLoading = false,
this.height = 200,
this.width,
this.borderRadius,
this.errorText,
this.showRemoveButton = true,
this.placeholderIcon,
this.placeholderText,
});
@override
State<ModernImagePicker> createState() => _ModernImagePickerState();
}
class _ModernImagePickerState extends State<ModernImagePicker>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationFast,
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
_fadeAnimation = Tween<double>(
begin: 1.0,
end: 0.8,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
bool get hasImage =>
widget.imageBytes != null ||
widget.imageFile != null ||
(widget.imageUrl != null && widget.imageUrl!.isNotEmpty);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
if (widget.label != null) ...[
Text(
widget.label!,
style: theme.textTheme.titleMedium?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing12),
],
// Image Container
GestureDetector(
onTapDown: (_) => _animationController.forward(),
onTapUp: (_) => _animationController.reverse(),
onTapCancel: () => _animationController.reverse(),
onTap: widget.onPickImage,
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: FadeTransition(
opacity: _fadeAnimation,
child: Container(
height: widget.height,
width: widget.width ?? double.infinity,
decoration: BoxDecoration(
borderRadius: widget.borderRadius ??
BorderRadius.circular(UIConstants.radius16),
border: Border.all(
color: widget.errorText != null
? colorScheme.error
: colorScheme.outline.withOpacity(0.3),
width: 2,
),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: widget.borderRadius ??
BorderRadius.circular(UIConstants.radius16),
child: Stack(
children: [
// Image or Placeholder
if (hasImage)
_buildImageWidget()
else
_buildPlaceholder(theme, colorScheme),
// Loading Overlay
if (widget.isLoading)
_buildLoadingOverlay(colorScheme),
// Remove Button
if (hasImage &&
widget.showRemoveButton &&
widget.onRemoveImage != null)
_buildRemoveButton(colorScheme),
],
),
),
),
),
);
},
),
),
// Hint Text
if (widget.hint != null) ...[
const SizedBox(height: UIConstants.spacing8),
Text(
widget.hint!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
// Error Text
if (widget.errorText != null) ...[
const SizedBox(height: UIConstants.spacing8),
Text(
widget.errorText!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.error,
),
),
],
// Action Buttons
const SizedBox(height: UIConstants.spacing16),
Row(
children: [
Expanded(
child: ModernButton(
text: hasImage ? 'Change Image' : 'Pick Image',
type: ModernButtonType.outline,
size: ModernButtonSize.medium,
icon: Icon(hasImage ? Icons.edit : Icons.add_photo_alternate),
onPressed: widget.isLoading ? null : widget.onPickImage,
isLoading: widget.isLoading,
),
),
if (hasImage && widget.onRemoveImage != null) ...[
const SizedBox(width: UIConstants.spacing12),
ModernButton(
text: 'Remove',
type: ModernButtonType.danger,
size: ModernButtonSize.medium,
icon: Icon(Icons.delete_outline),
onPressed: widget.isLoading ? null : widget.onRemoveImage,
),
],
],
),
],
);
}
Widget _buildImageWidget() {
if (widget.imageBytes != null) {
return Image.memory(
widget.imageBytes!,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
);
} else if (widget.imageFile != null) {
return Image.file(
widget.imageFile!,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
);
} else if (widget.imageUrl != null && widget.imageUrl!.isNotEmpty) {
return Image.network(
widget.imageUrl!,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, error, stackTrace) {
return _buildPlaceholder(
Theme.of(context), Theme.of(context).colorScheme);
},
);
}
return const SizedBox.shrink();
}
Widget _buildPlaceholder(ThemeData theme, ColorScheme colorScheme) {
return Container(
width: double.infinity,
height: double.infinity,
color: colorScheme.surfaceVariant.withOpacity(0.3),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
widget.placeholderIcon ?? Icons.add_photo_alternate,
size: UIConstants.iconSizeXLarge,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(height: UIConstants.spacing12),
Text(
widget.placeholderText ?? 'Tap to select image',
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
const SizedBox(height: UIConstants.spacing8),
Text(
'JPG, PNG, GIF up to 10MB',
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildLoadingOverlay(ColorScheme colorScheme) {
return Container(
width: double.infinity,
height: double.infinity,
color: colorScheme.surface.withOpacity(0.8),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: colorScheme.primary,
),
const SizedBox(height: UIConstants.spacing12),
Text(
'Uploading...',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurface,
),
),
],
),
),
);
}
Widget _buildRemoveButton(ColorScheme colorScheme) {
return Positioned(
top: UIConstants.spacing8,
right: UIConstants.spacing8,
child: Container(
decoration: BoxDecoration(
color: colorScheme.error,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: IconButton(
onPressed: widget.onRemoveImage,
icon: Icon(
Icons.close,
color: colorScheme.onError,
size: UIConstants.iconSizeSmall,
),
padding: const EdgeInsets.all(UIConstants.spacing4),
constraints: const BoxConstraints(
minWidth: 32,
minHeight: 32,
),
),
),
);
}
}

View File

@@ -0,0 +1,298 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../../../core/constants/ui_constants.dart';
class ModernTextField extends StatefulWidget {
final String? label;
final String? hint;
final String? helperText;
final String? errorText;
final TextEditingController? controller;
final TextInputType? keyboardType;
final bool obscureText;
final bool enabled;
final bool readOnly;
final int? maxLines;
final int? maxLength;
final Widget? prefixIcon;
final Widget? suffixIcon;
final VoidCallback? onSuffixIconPressed;
final String? Function(String?)? validator;
final void Function(String)? onChanged;
final void Function(String)? onSubmitted;
final List<TextInputFormatter>? inputFormatters;
final FocusNode? focusNode;
final VoidCallback? onTap;
final bool autofocus;
final TextCapitalization textCapitalization;
final TextInputAction? textInputAction;
final bool expands;
final double? height;
final EdgeInsetsGeometry? contentPadding;
const ModernTextField({
super.key,
this.label,
this.hint,
this.helperText,
this.errorText,
this.controller,
this.keyboardType,
this.obscureText = false,
this.enabled = true,
this.readOnly = false,
this.maxLines = 1,
this.maxLength,
this.prefixIcon,
this.suffixIcon,
this.onSuffixIconPressed,
this.validator,
this.onChanged,
this.onSubmitted,
this.inputFormatters,
this.focusNode,
this.onTap,
this.autofocus = false,
this.textCapitalization = TextCapitalization.none,
this.textInputAction,
this.expands = false,
this.height,
this.contentPadding,
});
@override
State<ModernTextField> createState() => _ModernTextFieldState();
}
class _ModernTextFieldState extends State<ModernTextField>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
bool _isFocused = false;
bool _hasError = false;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationFast,
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
_scaleAnimation = Tween<double>(
begin: 0.95,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _onFocusChange(bool hasFocus) {
setState(() {
_isFocused = hasFocus;
});
if (hasFocus) {
_animationController.forward();
} else {
_animationController.reverse();
}
}
void _onChanged(String value) {
setState(() {
_hasError = false;
});
if (widget.onChanged != null) {
widget.onChanged!(value);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.label != null) ...[
Padding(
padding: const EdgeInsets.only(bottom: UIConstants.spacing8),
child: Text(
widget.label!,
style: theme.textTheme.labelLarge?.copyWith(
color: _isFocused
? colorScheme.primary
: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w600,
),
),
),
],
Focus(
onFocusChange: _onFocusChange,
child: Container(
height: widget.height ?? UIConstants.inputHeightMedium,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(UIConstants.radius16),
color: _isFocused
? colorScheme.surfaceVariant.withOpacity(0.5)
: colorScheme.surfaceVariant.withOpacity(0.3),
border: Border.all(
color: _hasError
? colorScheme.error
: _isFocused
? colorScheme.primary
: colorScheme.outline.withOpacity(0.3),
width: _isFocused ? 2.0 : 1.5,
),
boxShadow: _isFocused
? [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
]
: null,
),
child: TextFormField(
controller: widget.controller,
focusNode: widget.focusNode,
keyboardType: widget.keyboardType,
obscureText: widget.obscureText,
enabled: widget.enabled,
readOnly: widget.readOnly,
maxLines: widget.maxLines,
maxLength: widget.maxLength,
autofocus: widget.autofocus,
textCapitalization: widget.textCapitalization,
textInputAction: widget.textInputAction,
expands: widget.expands,
inputFormatters: widget.inputFormatters,
validator: (value) {
if (widget.validator != null) {
final result = widget.validator!(value);
setState(() {
_hasError = result != null;
});
return result;
}
return null;
},
onChanged: _onChanged,
onFieldSubmitted: widget.onSubmitted,
onTap: widget.onTap,
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurface,
),
decoration: InputDecoration(
hintText: widget.hint,
hintStyle: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant.withOpacity(0.6),
),
prefixIcon: widget.prefixIcon != null
? Padding(
padding: const EdgeInsets.only(
left: UIConstants.spacing16),
child: IconTheme(
data: IconThemeData(
color: _isFocused
? colorScheme.primary
: colorScheme.onSurfaceVariant,
size: UIConstants.iconSizeMedium,
),
child: widget.prefixIcon!,
),
)
: null,
suffixIcon: widget.suffixIcon != null
? Padding(
padding: const EdgeInsets.only(
right: UIConstants.spacing16),
child: IconTheme(
data: IconThemeData(
color: _isFocused
? colorScheme.primary
: colorScheme.onSurfaceVariant,
size: UIConstants.iconSizeMedium,
),
child: widget.suffixIcon!,
),
)
: null,
border: InputBorder.none,
contentPadding: widget.contentPadding ??
const EdgeInsets.symmetric(
horizontal: UIConstants.spacing20,
vertical: UIConstants.spacing16,
),
counterText: '',
),
),
),
),
if (widget.helperText != null && !_hasError) ...[
Padding(
padding: const EdgeInsets.only(top: UIConstants.spacing8),
child: Text(
widget.helperText!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
],
if (widget.errorText != null && _hasError) ...[
Padding(
padding: const EdgeInsets.only(top: UIConstants.spacing8),
child: Row(
children: [
Icon(
Icons.error_outline,
size: UIConstants.iconSizeSmall,
color: colorScheme.error,
),
const SizedBox(width: UIConstants.spacing8),
Expanded(
child: Text(
widget.errorText!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.error,
),
),
),
],
),
),
],
],
),
);
},
);
}
}