baseproject
This commit is contained in:
342
base_project/lib/shared/widgets/buttons/modern_button.dart
Normal file
342
base_project/lib/shared/widgets/buttons/modern_button.dart
Normal file
@@ -0,0 +1,342 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/constants/ui_constants.dart';
|
||||
|
||||
enum ModernButtonType {
|
||||
primary,
|
||||
secondary,
|
||||
outline,
|
||||
text,
|
||||
danger,
|
||||
}
|
||||
|
||||
enum ModernButtonSize {
|
||||
small,
|
||||
medium,
|
||||
large,
|
||||
}
|
||||
|
||||
class ModernButton extends StatefulWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPressed;
|
||||
final ModernButtonType type;
|
||||
final ModernButtonSize size;
|
||||
final bool isLoading;
|
||||
final bool isDisabled;
|
||||
final Widget? icon;
|
||||
final bool isIconOnly;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final BorderRadius? borderRadius;
|
||||
final String? tooltip;
|
||||
|
||||
const ModernButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.onPressed,
|
||||
this.type = ModernButtonType.primary,
|
||||
this.size = ModernButtonSize.medium,
|
||||
this.isLoading = false,
|
||||
this.isDisabled = false,
|
||||
this.icon,
|
||||
this.isIconOnly = false,
|
||||
this.width,
|
||||
this.height,
|
||||
this.padding,
|
||||
this.borderRadius,
|
||||
this.tooltip,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ModernButton> createState() => _ModernButtonState();
|
||||
}
|
||||
|
||||
class _ModernButtonState extends State<ModernButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<double> _elevationAnimation;
|
||||
bool _isPressed = false;
|
||||
|
||||
@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.curveFast,
|
||||
));
|
||||
|
||||
_elevationAnimation = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 0.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: UIConstants.curveFast,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onTapDown(TapDownDetails details) {
|
||||
if (!widget.isDisabled && !widget.isLoading) {
|
||||
setState(() {
|
||||
_isPressed = true;
|
||||
});
|
||||
_animationController.forward();
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapUp(TapUpDetails details) {
|
||||
if (!widget.isDisabled && !widget.isLoading) {
|
||||
setState(() {
|
||||
_isPressed = false;
|
||||
});
|
||||
_animationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapCancel() {
|
||||
if (!widget.isDisabled && !widget.isLoading) {
|
||||
setState(() {
|
||||
_isPressed = false;
|
||||
});
|
||||
_animationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
bool get _canPress =>
|
||||
!widget.isDisabled && !widget.isLoading && widget.onPressed != null;
|
||||
|
||||
Color _getBackgroundColor(ThemeData theme) {
|
||||
if (!_canPress) {
|
||||
return theme.colorScheme.surfaceVariant;
|
||||
}
|
||||
|
||||
switch (widget.type) {
|
||||
case ModernButtonType.primary:
|
||||
return theme.colorScheme.primary;
|
||||
case ModernButtonType.secondary:
|
||||
return theme.colorScheme.secondary;
|
||||
case ModernButtonType.outline:
|
||||
case ModernButtonType.text:
|
||||
return Colors.transparent;
|
||||
case ModernButtonType.danger:
|
||||
return theme.colorScheme.error;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getTextColor(ThemeData theme) {
|
||||
if (!_canPress) {
|
||||
return theme.colorScheme.onSurfaceVariant;
|
||||
}
|
||||
|
||||
switch (widget.type) {
|
||||
case ModernButtonType.primary:
|
||||
case ModernButtonType.secondary:
|
||||
case ModernButtonType.danger:
|
||||
return theme.colorScheme.onPrimary;
|
||||
case ModernButtonType.outline:
|
||||
return theme.colorScheme.primary;
|
||||
case ModernButtonType.text:
|
||||
return theme.colorScheme.primary;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getBorderColor(ThemeData theme) {
|
||||
if (!_canPress) {
|
||||
return theme.colorScheme.outline;
|
||||
}
|
||||
|
||||
switch (widget.type) {
|
||||
case ModernButtonType.outline:
|
||||
return theme.colorScheme.primary;
|
||||
case ModernButtonType.danger:
|
||||
return theme.colorScheme.error;
|
||||
default:
|
||||
return Colors.transparent;
|
||||
}
|
||||
}
|
||||
|
||||
double _getHeight() {
|
||||
if (widget.height != null) return widget.height!;
|
||||
|
||||
switch (widget.size) {
|
||||
case ModernButtonSize.small:
|
||||
return UIConstants.buttonHeightSmall +
|
||||
4; // extra room to avoid text clip
|
||||
case ModernButtonSize.medium:
|
||||
return UIConstants.buttonHeightMedium + 4;
|
||||
case ModernButtonSize.large:
|
||||
return UIConstants.buttonHeightLarge + 4;
|
||||
}
|
||||
}
|
||||
|
||||
EdgeInsetsGeometry _getPadding() {
|
||||
if (widget.padding != null) return widget.padding!;
|
||||
|
||||
switch (widget.size) {
|
||||
case ModernButtonSize.small:
|
||||
return const EdgeInsets.symmetric(
|
||||
horizontal: UIConstants.spacing16,
|
||||
vertical: UIConstants.spacing6,
|
||||
);
|
||||
case ModernButtonSize.medium:
|
||||
return const EdgeInsets.symmetric(
|
||||
horizontal: UIConstants.spacing24,
|
||||
vertical: UIConstants.spacing14,
|
||||
);
|
||||
case ModernButtonSize.large:
|
||||
return const EdgeInsets.symmetric(
|
||||
horizontal: UIConstants.spacing32,
|
||||
vertical: UIConstants.spacing18,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double _getBorderRadius() {
|
||||
if (widget.borderRadius != null) {
|
||||
return widget.borderRadius!.topLeft.x;
|
||||
}
|
||||
|
||||
switch (widget.size) {
|
||||
case ModernButtonSize.small:
|
||||
return UIConstants.radius8;
|
||||
case ModernButtonSize.medium:
|
||||
return UIConstants.radius16;
|
||||
case ModernButtonSize.large:
|
||||
return UIConstants.radius20;
|
||||
}
|
||||
}
|
||||
|
||||
TextStyle _getTextStyle(ThemeData theme) {
|
||||
switch (widget.size) {
|
||||
case ModernButtonSize.small:
|
||||
return theme.textTheme.labelMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
) ??
|
||||
const TextStyle();
|
||||
case ModernButtonSize.medium:
|
||||
return theme.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
) ??
|
||||
const TextStyle();
|
||||
case ModernButtonSize.large:
|
||||
return theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
) ??
|
||||
const TextStyle();
|
||||
}
|
||||
}
|
||||
|
||||
@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: GestureDetector(
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
onTapCancel: _onTapCancel,
|
||||
child: Container(
|
||||
width: widget.width,
|
||||
height: _getHeight(),
|
||||
decoration: BoxDecoration(
|
||||
color: _getBackgroundColor(theme),
|
||||
borderRadius: BorderRadius.circular(_getBorderRadius()),
|
||||
border: Border.all(
|
||||
color: _getBorderColor(theme),
|
||||
width: widget.type == ModernButtonType.outline ? 1.5 : 0,
|
||||
),
|
||||
boxShadow: _canPress
|
||||
? [
|
||||
BoxShadow(
|
||||
color: _getBackgroundColor(theme).withOpacity(0.3),
|
||||
blurRadius: 8 * _elevationAnimation.value,
|
||||
offset: Offset(0, 4 * _elevationAnimation.value),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: _canPress ? widget.onPressed : null,
|
||||
borderRadius: BorderRadius.circular(_getBorderRadius()),
|
||||
child: Container(
|
||||
padding: _getPadding(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.isLoading) ...[
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
_getTextColor(theme),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!widget.isIconOnly) ...[
|
||||
const SizedBox(width: UIConstants.spacing12),
|
||||
],
|
||||
] else ...[
|
||||
if (widget.icon != null && !widget.isIconOnly) ...[
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: _getTextColor(theme),
|
||||
size: _getHeight() * 0.4,
|
||||
),
|
||||
child: widget.icon!,
|
||||
),
|
||||
const SizedBox(width: UIConstants.spacing12),
|
||||
],
|
||||
],
|
||||
if (!widget.isIconOnly) ...[
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.text,
|
||||
style: _getTextStyle(theme).copyWith(
|
||||
color: _getTextColor(theme),
|
||||
height: 1.2,
|
||||
),
|
||||
strutStyle: const StrutStyle(
|
||||
height: 1.2,
|
||||
forceStrutHeight: true,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
176
base_project/lib/shared/widgets/buttons/quick_action_button.dart
Normal file
176
base_project/lib/shared/widgets/buttons/quick_action_button.dart
Normal file
@@ -0,0 +1,176 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/constants/ui_constants.dart';
|
||||
|
||||
class QuickActionButton extends StatefulWidget {
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final VoidCallback? onTap;
|
||||
final Color? backgroundColor;
|
||||
final Color? iconColor;
|
||||
final Color? labelColor;
|
||||
final double? size;
|
||||
final bool isLoading;
|
||||
final bool isDisabled;
|
||||
|
||||
const QuickActionButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.icon,
|
||||
this.onTap,
|
||||
this.backgroundColor,
|
||||
this.iconColor,
|
||||
this.labelColor,
|
||||
this.size,
|
||||
this.isLoading = false,
|
||||
this.isDisabled = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<QuickActionButton> createState() => _QuickActionButtonState();
|
||||
}
|
||||
|
||||
class _QuickActionButtonState extends State<QuickActionButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<double> _elevationAnimation;
|
||||
|
||||
@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.curveFast,
|
||||
));
|
||||
|
||||
_elevationAnimation = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 0.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: UIConstants.curveFast,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onTapDown(TapDownDetails details) {
|
||||
if (widget.onTap != null && !widget.isDisabled && !widget.isLoading) {
|
||||
_animationController.forward();
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapUp(TapUpDetails details) {
|
||||
if (widget.onTap != null && !widget.isDisabled && !widget.isLoading) {
|
||||
_animationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapCancel() {
|
||||
if (widget.onTap != null && !widget.isDisabled && !widget.isLoading) {
|
||||
_animationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
final buttonSize = widget.size ?? UIConstants.logoSizeMedium;
|
||||
final backgroundColor =
|
||||
widget.backgroundColor ?? colorScheme.primaryContainer;
|
||||
final iconColor = widget.iconColor ?? colorScheme.onPrimaryContainer;
|
||||
final labelColor = widget.labelColor ?? colorScheme.onSurface;
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
builder: (context, child) {
|
||||
return Transform.scale(
|
||||
scale: _scaleAnimation.value,
|
||||
child: GestureDetector(
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
onTapCancel: _onTapCancel,
|
||||
child: Container(
|
||||
width: buttonSize,
|
||||
height: buttonSize,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(UIConstants.radius16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: backgroundColor.withOpacity(0.3),
|
||||
blurRadius: 8 * _elevationAnimation.value,
|
||||
offset: Offset(0, 4 * _elevationAnimation.value),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: (widget.onTap != null &&
|
||||
!widget.isDisabled &&
|
||||
!widget.isLoading)
|
||||
? widget.onTap
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(UIConstants.radius16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UIConstants.spacing12),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Icon
|
||||
if (widget.isLoading)
|
||||
SizedBox(
|
||||
width: buttonSize * 0.3,
|
||||
height: buttonSize * 0.3,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(iconColor),
|
||||
),
|
||||
)
|
||||
else
|
||||
Icon(
|
||||
widget.icon,
|
||||
color: iconColor,
|
||||
size: buttonSize * 0.4,
|
||||
),
|
||||
|
||||
const SizedBox(height: UIConstants.spacing8),
|
||||
|
||||
// Label
|
||||
Text(
|
||||
widget.label,
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: labelColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user