Implement core package refactoring: BaseLogic consolidation and new CoreButton/CoreLoadingIndicator
Co-authored-by: mes71 <53784874+mes71@users.noreply.github.com>
This commit is contained in:
@@ -1,31 +1,81 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
class BaseLogic extends GetxController {
|
||||
/// Consolidated base logic controller that provides common functionality
|
||||
/// for pages with search and filter capabilities.
|
||||
///
|
||||
/// This replaces the duplicate BaseLogic classes across different packages.
|
||||
class BasePageLogic extends GetxController {
|
||||
final RxBool isFilterSelected = false.obs;
|
||||
final RxBool isSearchSelected = false.obs;
|
||||
final RxnString searchValue = RxnString();
|
||||
final TextEditingController textEditingController = TextEditingController();
|
||||
final TextEditingController searchTextController = TextEditingController();
|
||||
|
||||
void setSearchCallback(void Function(String?)? onSearchChanged) {
|
||||
// Debounce time configuration
|
||||
static const Duration _defaultDebounceTime = Duration(milliseconds: 600);
|
||||
|
||||
/// Sets up search callback with debouncing
|
||||
/// [onSearchChanged] will be called when search value changes after debounce delay
|
||||
/// [debounceTime] custom debounce duration, defaults to 600ms
|
||||
void setSearchCallback(
|
||||
void Function(String?)? onSearchChanged, {
|
||||
Duration debounceTime = _defaultDebounceTime,
|
||||
}) {
|
||||
debounce<String?>(searchValue, (val) {
|
||||
if (val != null && val.trim().isNotEmpty) {
|
||||
onSearchChanged?.call(val);
|
||||
} else {
|
||||
// Call with null/empty to handle search clear
|
||||
onSearchChanged?.call(null);
|
||||
}
|
||||
}, time: const Duration(milliseconds: 600));
|
||||
}, time: debounceTime);
|
||||
}
|
||||
|
||||
/// Toggles search visibility state
|
||||
void toggleSearch() {
|
||||
isSearchSelected.value = !isSearchSelected.value;
|
||||
|
||||
// Clear search when hiding
|
||||
if (!isSearchSelected.value) {
|
||||
clearSearch();
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears search input and resets state
|
||||
void clearSearch() {
|
||||
textEditingController.clear();
|
||||
searchTextController.clear();
|
||||
searchValue.value = null;
|
||||
isSearchSelected.value = false;
|
||||
}
|
||||
|
||||
/// Toggles filter selection state
|
||||
void toggleFilter() {
|
||||
isFilterSelected.value = !isFilterSelected.value;
|
||||
}
|
||||
|
||||
/// Resets all states to initial values
|
||||
void resetStates() {
|
||||
isFilterSelected.value = false;
|
||||
isSearchSelected.value = false;
|
||||
clearSearch();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
// Bind search controller to reactive value
|
||||
searchTextController.addListener(() {
|
||||
searchValue.value = searchTextController.text;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
searchTextController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
/// Backward compatibility alias - will be deprecated
|
||||
@Deprecated('Use BasePageLogic instead. This will be removed in future versions.')
|
||||
typedef BaseLogic = BasePageLogic;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export 'animated_fab.dart';
|
||||
export 'core_button.dart';
|
||||
export 'elevated.dart';
|
||||
export 'fab.dart';
|
||||
export 'fab_outlined.dart';
|
||||
|
||||
407
packages/core/lib/presentation/widget/buttons/core_button.dart
Normal file
407
packages/core/lib/presentation/widget/buttons/core_button.dart
Normal file
@@ -0,0 +1,407 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
/// Button variant types for consistent styling
|
||||
enum CoreButtonVariant {
|
||||
/// Filled button with primary background color
|
||||
primary,
|
||||
/// Filled button with secondary background color
|
||||
secondary,
|
||||
/// Button with transparent background and border
|
||||
outlined,
|
||||
/// Button with transparent background, no border
|
||||
text,
|
||||
/// Destructive action button (red theme)
|
||||
destructive,
|
||||
}
|
||||
|
||||
/// Button size presets
|
||||
enum CoreButtonSize {
|
||||
/// Small button - height 32
|
||||
small,
|
||||
/// Medium button - height 40 (default)
|
||||
medium,
|
||||
/// Large button - height 56
|
||||
large,
|
||||
}
|
||||
|
||||
/// A unified, configurable button widget that replaces RElevated, ROutlinedElevated, etc.
|
||||
///
|
||||
/// This widget provides a consistent API and theming system across the entire app.
|
||||
/// It supports different variants, sizes, loading states, and full customization.
|
||||
class CoreButton extends StatelessWidget {
|
||||
/// Button text content
|
||||
final String? text;
|
||||
|
||||
/// Custom child widget (overrides text if provided)
|
||||
final Widget? child;
|
||||
|
||||
/// Button press callback
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// Button style variant
|
||||
final CoreButtonVariant variant;
|
||||
|
||||
/// Button size preset
|
||||
final CoreButtonSize size;
|
||||
|
||||
/// Custom width (overrides size preset)
|
||||
final double? width;
|
||||
|
||||
/// Custom height (overrides size preset)
|
||||
final double? height;
|
||||
|
||||
/// Whether button should take full width
|
||||
final bool isFullWidth;
|
||||
|
||||
/// Loading state - shows progress indicator
|
||||
final bool isLoading;
|
||||
|
||||
/// Progress value for loading indicator (0.0 to 1.0, null for indeterminate)
|
||||
final double? progress;
|
||||
|
||||
/// Custom text style (overrides theme)
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// Custom background color (overrides variant theme)
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Custom foreground color (overrides variant theme)
|
||||
final Color? foregroundColor;
|
||||
|
||||
/// Custom border color for outlined variant
|
||||
final Color? borderColor;
|
||||
|
||||
/// Border radius (default: 8.0)
|
||||
final double borderRadius;
|
||||
|
||||
/// Leading icon
|
||||
final Widget? icon;
|
||||
|
||||
/// Icon placement
|
||||
final bool iconAtEnd;
|
||||
|
||||
/// Spacing between icon and text
|
||||
final double iconSpacing;
|
||||
|
||||
const CoreButton({
|
||||
super.key,
|
||||
this.text,
|
||||
this.child,
|
||||
required this.onPressed,
|
||||
this.variant = CoreButtonVariant.primary,
|
||||
this.size = CoreButtonSize.medium,
|
||||
this.width,
|
||||
this.height,
|
||||
this.isFullWidth = false,
|
||||
this.isLoading = false,
|
||||
this.progress,
|
||||
this.textStyle,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.borderColor,
|
||||
this.borderRadius = 8.0,
|
||||
this.icon,
|
||||
this.iconAtEnd = false,
|
||||
this.iconSpacing = 8.0,
|
||||
}) : assert(text != null || child != null, 'Either text or child must be provided');
|
||||
|
||||
/// Creates a primary filled button
|
||||
const CoreButton.primary({
|
||||
super.key,
|
||||
this.text,
|
||||
this.child,
|
||||
required this.onPressed,
|
||||
this.size = CoreButtonSize.medium,
|
||||
this.width,
|
||||
this.height,
|
||||
this.isFullWidth = false,
|
||||
this.isLoading = false,
|
||||
this.progress,
|
||||
this.textStyle,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.borderRadius = 8.0,
|
||||
this.icon,
|
||||
this.iconAtEnd = false,
|
||||
this.iconSpacing = 8.0,
|
||||
}) : variant = CoreButtonVariant.primary,
|
||||
borderColor = null,
|
||||
assert(text != null || child != null, 'Either text or child must be provided');
|
||||
|
||||
/// Creates an outlined button
|
||||
const CoreButton.outlined({
|
||||
super.key,
|
||||
this.text,
|
||||
this.child,
|
||||
required this.onPressed,
|
||||
this.size = CoreButtonSize.medium,
|
||||
this.width,
|
||||
this.height,
|
||||
this.isFullWidth = false,
|
||||
this.isLoading = false,
|
||||
this.progress,
|
||||
this.textStyle,
|
||||
this.foregroundColor,
|
||||
this.borderColor,
|
||||
this.borderRadius = 8.0,
|
||||
this.icon,
|
||||
this.iconAtEnd = false,
|
||||
this.iconSpacing = 8.0,
|
||||
}) : variant = CoreButtonVariant.outlined,
|
||||
backgroundColor = null,
|
||||
assert(text != null || child != null, 'Either text or child must be provided');
|
||||
|
||||
/// Creates a text button with no background
|
||||
const CoreButton.text({
|
||||
super.key,
|
||||
this.text,
|
||||
this.child,
|
||||
required this.onPressed,
|
||||
this.size = CoreButtonSize.medium,
|
||||
this.width,
|
||||
this.height,
|
||||
this.isFullWidth = false,
|
||||
this.isLoading = false,
|
||||
this.progress,
|
||||
this.textStyle,
|
||||
this.foregroundColor,
|
||||
this.borderRadius = 8.0,
|
||||
this.icon,
|
||||
this.iconAtEnd = false,
|
||||
this.iconSpacing = 8.0,
|
||||
}) : variant = CoreButtonVariant.text,
|
||||
backgroundColor = null,
|
||||
borderColor = null,
|
||||
assert(text != null || child != null, 'Either text or child must be provided');
|
||||
|
||||
/// Creates a destructive action button
|
||||
const CoreButton.destructive({
|
||||
super.key,
|
||||
this.text,
|
||||
this.child,
|
||||
required this.onPressed,
|
||||
this.size = CoreButtonSize.medium,
|
||||
this.width,
|
||||
this.height,
|
||||
this.isFullWidth = false,
|
||||
this.isLoading = false,
|
||||
this.progress,
|
||||
this.textStyle,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.borderRadius = 8.0,
|
||||
this.icon,
|
||||
this.iconAtEnd = false,
|
||||
this.iconSpacing = 8.0,
|
||||
}) : variant = CoreButtonVariant.destructive,
|
||||
borderColor = null,
|
||||
assert(text != null || child != null, 'Either text or child must be provided');
|
||||
|
||||
/// Gets button dimensions based on size preset
|
||||
Size get _buttonSize {
|
||||
final buttonWidth = isFullWidth ? double.infinity : (width ?? _defaultWidth);
|
||||
final buttonHeight = height ?? _defaultHeight;
|
||||
return Size(buttonWidth, buttonHeight);
|
||||
}
|
||||
|
||||
double get _defaultWidth {
|
||||
switch (size) {
|
||||
case CoreButtonSize.small:
|
||||
return 120.0;
|
||||
case CoreButtonSize.medium:
|
||||
return 150.0;
|
||||
case CoreButtonSize.large:
|
||||
return 200.0;
|
||||
}
|
||||
}
|
||||
|
||||
double get _defaultHeight {
|
||||
switch (size) {
|
||||
case CoreButtonSize.small:
|
||||
return 32.0;
|
||||
case CoreButtonSize.medium:
|
||||
return 40.0;
|
||||
case CoreButtonSize.large:
|
||||
return 56.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets theme colors based on variant
|
||||
_ButtonTheme get _theme {
|
||||
switch (variant) {
|
||||
case CoreButtonVariant.primary:
|
||||
return _ButtonTheme(
|
||||
backgroundColor: backgroundColor ?? AppColor.blueNormal,
|
||||
foregroundColor: foregroundColor ?? Colors.white,
|
||||
borderColor: null,
|
||||
);
|
||||
case CoreButtonVariant.secondary:
|
||||
return _ButtonTheme(
|
||||
backgroundColor: backgroundColor ?? AppColor.greyNormal,
|
||||
foregroundColor: foregroundColor ?? AppColor.textColor,
|
||||
borderColor: null,
|
||||
);
|
||||
case CoreButtonVariant.outlined:
|
||||
return _ButtonTheme(
|
||||
backgroundColor: backgroundColor ?? Colors.transparent,
|
||||
foregroundColor: foregroundColor ?? (borderColor ?? AppColor.blueNormal),
|
||||
borderColor: borderColor ?? AppColor.blueNormal,
|
||||
);
|
||||
case CoreButtonVariant.text:
|
||||
return _ButtonTheme(
|
||||
backgroundColor: backgroundColor ?? Colors.transparent,
|
||||
foregroundColor: foregroundColor ?? AppColor.blueNormal,
|
||||
borderColor: null,
|
||||
);
|
||||
case CoreButtonVariant.destructive:
|
||||
return _ButtonTheme(
|
||||
backgroundColor: backgroundColor ?? AppColor.error,
|
||||
foregroundColor: foregroundColor ?? Colors.white,
|
||||
borderColor: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TextStyle get _textTheme {
|
||||
final baseStyle = textStyle ?? AppFonts.yekan16;
|
||||
return baseStyle.copyWith(color: _theme.foregroundColor);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
if (isLoading) {
|
||||
return SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(_theme.foregroundColor),
|
||||
value: progress,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final content = child ?? Text(text!, style: _textTheme);
|
||||
|
||||
if (icon != null) {
|
||||
final iconWidget = IconTheme(
|
||||
data: IconThemeData(color: _theme.foregroundColor, size: 18),
|
||||
child: icon!,
|
||||
);
|
||||
|
||||
if (iconAtEnd) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
content,
|
||||
SizedBox(width: iconSpacing),
|
||||
iconWidget,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
iconWidget,
|
||||
SizedBox(width: iconSpacing),
|
||||
content,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEnabled = onPressed != null && !isLoading;
|
||||
final theme = _theme;
|
||||
final buttonSize = _buttonSize;
|
||||
|
||||
if (variant == CoreButtonVariant.outlined) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(
|
||||
width: buttonSize.width,
|
||||
height: buttonSize.height,
|
||||
),
|
||||
child: OutlinedButton(
|
||||
onPressed: isEnabled ? onPressed : null,
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: theme.backgroundColor,
|
||||
foregroundColor: theme.foregroundColor,
|
||||
side: BorderSide(
|
||||
color: isEnabled ? theme.borderColor! : theme.borderColor!.withOpacity(0.38),
|
||||
width: 1.5,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
textStyle: _textTheme,
|
||||
),
|
||||
child: _buildContent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (variant == CoreButtonVariant.text) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(
|
||||
width: buttonSize.width,
|
||||
height: buttonSize.height,
|
||||
),
|
||||
child: TextButton(
|
||||
onPressed: isEnabled ? onPressed : null,
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: theme.backgroundColor,
|
||||
foregroundColor: theme.foregroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
textStyle: _textTheme,
|
||||
),
|
||||
child: _buildContent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Default to ElevatedButton for primary, secondary, destructive
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(
|
||||
width: buttonSize.width,
|
||||
height: buttonSize.height,
|
||||
),
|
||||
child: ElevatedButton(
|
||||
onPressed: isEnabled ? onPressed : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.backgroundColor,
|
||||
foregroundColor: theme.foregroundColor,
|
||||
disabledBackgroundColor: theme.backgroundColor.withOpacity(0.38),
|
||||
disabledForegroundColor: theme.foregroundColor.withOpacity(0.38),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
textStyle: _textTheme,
|
||||
),
|
||||
child: _buildContent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal theme data for button variants
|
||||
class _ButtonTheme {
|
||||
final Color backgroundColor;
|
||||
final Color foregroundColor;
|
||||
final Color? borderColor;
|
||||
|
||||
const _ButtonTheme({
|
||||
required this.backgroundColor,
|
||||
required this.foregroundColor,
|
||||
this.borderColor,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
/// Loading indicator variant types
|
||||
enum CoreLoadingVariant {
|
||||
/// Material Design circular progress indicator
|
||||
material,
|
||||
/// iOS style activity indicator
|
||||
cupertino,
|
||||
/// Custom Lottie animation
|
||||
lottie,
|
||||
/// Linear progress indicator
|
||||
linear,
|
||||
}
|
||||
|
||||
/// Loading indicator size presets
|
||||
enum CoreLoadingSize {
|
||||
/// Small - 16x16
|
||||
small,
|
||||
/// Medium - 24x24 (default)
|
||||
medium,
|
||||
/// Large - 32x32
|
||||
large,
|
||||
/// Extra large - 48x48
|
||||
extraLarge,
|
||||
}
|
||||
|
||||
/// A unified loading indicator widget that replaces inconsistent loading patterns
|
||||
///
|
||||
/// This widget provides consistent loading states across the entire app with
|
||||
/// support for different variants, sizes, colors, and text labels.
|
||||
class CoreLoadingIndicator extends StatelessWidget {
|
||||
/// Loading indicator variant
|
||||
final CoreLoadingVariant variant;
|
||||
|
||||
/// Size preset
|
||||
final CoreLoadingSize size;
|
||||
|
||||
/// Custom width (overrides size preset)
|
||||
final double? width;
|
||||
|
||||
/// Custom height (overrides size preset)
|
||||
final double? height;
|
||||
|
||||
/// Indicator color
|
||||
final Color? color;
|
||||
|
||||
/// Progress value for determinate indicators (0.0 to 1.0)
|
||||
final double? value;
|
||||
|
||||
/// Stroke width for circular indicators
|
||||
final double? strokeWidth;
|
||||
|
||||
/// Loading text label
|
||||
final String? label;
|
||||
|
||||
/// Text style for label
|
||||
final TextStyle? labelStyle;
|
||||
|
||||
/// Spacing between indicator and label
|
||||
final double labelSpacing;
|
||||
|
||||
/// Label position relative to indicator
|
||||
final CrossAxisAlignment labelAlignment;
|
||||
|
||||
const CoreLoadingIndicator({
|
||||
super.key,
|
||||
this.variant = CoreLoadingVariant.material,
|
||||
this.size = CoreLoadingSize.medium,
|
||||
this.width,
|
||||
this.height,
|
||||
this.color,
|
||||
this.value,
|
||||
this.strokeWidth,
|
||||
this.label,
|
||||
this.labelStyle,
|
||||
this.labelSpacing = 12.0,
|
||||
this.labelAlignment = CrossAxisAlignment.center,
|
||||
});
|
||||
|
||||
/// Creates a small Material loading indicator
|
||||
const CoreLoadingIndicator.small({
|
||||
super.key,
|
||||
this.color,
|
||||
this.value,
|
||||
this.strokeWidth,
|
||||
this.label,
|
||||
this.labelStyle,
|
||||
this.labelSpacing = 8.0,
|
||||
this.labelAlignment = CrossAxisAlignment.center,
|
||||
}) : variant = CoreLoadingVariant.material,
|
||||
size = CoreLoadingSize.small,
|
||||
width = null,
|
||||
height = null;
|
||||
|
||||
/// Creates a Cupertino style activity indicator
|
||||
const CoreLoadingIndicator.cupertino({
|
||||
super.key,
|
||||
this.size = CoreLoadingSize.medium,
|
||||
this.width,
|
||||
this.height,
|
||||
this.color,
|
||||
this.label,
|
||||
this.labelStyle,
|
||||
this.labelSpacing = 12.0,
|
||||
this.labelAlignment = CrossAxisAlignment.center,
|
||||
}) : variant = CoreLoadingVariant.cupertino,
|
||||
value = null,
|
||||
strokeWidth = null;
|
||||
|
||||
/// Creates a linear progress indicator
|
||||
const CoreLoadingIndicator.linear({
|
||||
super.key,
|
||||
this.width,
|
||||
this.height = 4.0,
|
||||
this.color,
|
||||
this.value,
|
||||
this.label,
|
||||
this.labelStyle,
|
||||
this.labelSpacing = 8.0,
|
||||
this.labelAlignment = CrossAxisAlignment.start,
|
||||
}) : variant = CoreLoadingVariant.linear,
|
||||
size = CoreLoadingSize.medium,
|
||||
strokeWidth = null;
|
||||
|
||||
/// Creates a large Lottie animation loading indicator
|
||||
const CoreLoadingIndicator.lottie({
|
||||
super.key,
|
||||
this.size = CoreLoadingSize.large,
|
||||
this.width,
|
||||
this.height,
|
||||
this.label,
|
||||
this.labelStyle,
|
||||
this.labelSpacing = 16.0,
|
||||
this.labelAlignment = CrossAxisAlignment.center,
|
||||
}) : variant = CoreLoadingVariant.lottie,
|
||||
color = null,
|
||||
value = null,
|
||||
strokeWidth = null;
|
||||
|
||||
/// Gets size dimensions based on size preset
|
||||
Size get _indicatorSize {
|
||||
final sizeValue = width ?? height ?? _defaultSize;
|
||||
return Size(sizeValue, sizeValue);
|
||||
}
|
||||
|
||||
double get _defaultSize {
|
||||
switch (size) {
|
||||
case CoreLoadingSize.small:
|
||||
return 16.0;
|
||||
case CoreLoadingSize.medium:
|
||||
return 24.0;
|
||||
case CoreLoadingSize.large:
|
||||
return 32.0;
|
||||
case CoreLoadingSize.extraLarge:
|
||||
return 48.0;
|
||||
}
|
||||
}
|
||||
|
||||
Color get _indicatorColor {
|
||||
return color ?? AppColor.blueNormal;
|
||||
}
|
||||
|
||||
Widget _buildIndicator() {
|
||||
final indicatorSize = _indicatorSize;
|
||||
|
||||
switch (variant) {
|
||||
case CoreLoadingVariant.material:
|
||||
return SizedBox(
|
||||
width: indicatorSize.width,
|
||||
height: indicatorSize.height,
|
||||
child: CircularProgressIndicator(
|
||||
value: value,
|
||||
strokeWidth: strokeWidth ?? (indicatorSize.width * 0.08).clamp(1.5, 4.0),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(_indicatorColor),
|
||||
),
|
||||
);
|
||||
|
||||
case CoreLoadingVariant.cupertino:
|
||||
return SizedBox(
|
||||
width: indicatorSize.width,
|
||||
height: indicatorSize.height,
|
||||
child: CupertinoActivityIndicator(
|
||||
color: _indicatorColor,
|
||||
radius: indicatorSize.width * 0.4,
|
||||
),
|
||||
);
|
||||
|
||||
case CoreLoadingVariant.linear:
|
||||
return SizedBox(
|
||||
width: width ?? 200.0,
|
||||
height: height ?? 4.0,
|
||||
child: LinearProgressIndicator(
|
||||
value: value,
|
||||
backgroundColor: _indicatorColor.withOpacity(0.2),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(_indicatorColor),
|
||||
),
|
||||
);
|
||||
|
||||
case CoreLoadingVariant.lottie:
|
||||
try {
|
||||
return Assets.anim.loading.lottie(
|
||||
width: indicatorSize.width,
|
||||
height: indicatorSize.height,
|
||||
);
|
||||
} catch (e) {
|
||||
// Fallback to Material indicator if Lottie fails
|
||||
return SizedBox(
|
||||
width: indicatorSize.width,
|
||||
height: indicatorSize.height,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: strokeWidth ?? 3.0,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(_indicatorColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildLabel() {
|
||||
if (label == null) return const SizedBox.shrink();
|
||||
|
||||
return Text(
|
||||
label!,
|
||||
style: labelStyle ?? AppFonts.yekan14.copyWith(
|
||||
color: AppColor.textColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final indicator = _buildIndicator();
|
||||
final labelWidget = _buildLabel();
|
||||
|
||||
if (label == null) {
|
||||
return indicator;
|
||||
}
|
||||
|
||||
if (variant == CoreLoadingVariant.linear) {
|
||||
return Column(
|
||||
crossAxisAlignment: labelAlignment,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
labelWidget,
|
||||
SizedBox(height: labelSpacing),
|
||||
indicator,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: labelAlignment,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
indicator,
|
||||
SizedBox(height: labelSpacing),
|
||||
labelWidget,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A full-screen loading overlay widget
|
||||
class CoreLoadingOverlay extends StatelessWidget {
|
||||
/// Loading indicator to display
|
||||
final CoreLoadingIndicator indicator;
|
||||
|
||||
/// Background color of the overlay
|
||||
final Color backgroundColor;
|
||||
|
||||
/// Whether the overlay should block user interaction
|
||||
final bool barrierDismissible;
|
||||
|
||||
const CoreLoadingOverlay({
|
||||
super.key,
|
||||
this.indicator = const CoreLoadingIndicator.lottie(
|
||||
label: 'در حال بارگذاری...',
|
||||
),
|
||||
this.backgroundColor = Colors.black54,
|
||||
this.barrierDismissible = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: backgroundColor,
|
||||
child: Center(child: indicator),
|
||||
);
|
||||
}
|
||||
|
||||
/// Shows a loading overlay on top of current screen
|
||||
static void show(
|
||||
BuildContext context, {
|
||||
CoreLoadingIndicator? indicator,
|
||||
Color backgroundColor = Colors.black54,
|
||||
bool barrierDismissible = false,
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: barrierDismissible,
|
||||
barrierColor: Colors.transparent,
|
||||
builder: (context) => CoreLoadingOverlay(
|
||||
indicator: indicator ?? const CoreLoadingIndicator.lottie(
|
||||
label: 'در حال بارگذاری...',
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
barrierDismissible: barrierDismissible,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Hides the currently displayed loading overlay
|
||||
static void hide(BuildContext context) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export 'core_loading_indicator.dart';
|
||||
@@ -11,8 +11,8 @@ export 'base_page/widgets/back_ground_widget.dart';
|
||||
export 'base_page/widgets/breadcrumb.dart';
|
||||
export 'base_page/widgets/search_widget.dart';
|
||||
|
||||
|
||||
//buttons
|
||||
//buttons - enhanced core widgets
|
||||
export 'buttons/core_button.dart';
|
||||
export 'buttons/buttons.dart';
|
||||
export 'card/card_icon_widget.dart';
|
||||
export 'chips/r_chips.dart';
|
||||
@@ -24,6 +24,8 @@ export 'draggable_bottom_sheet/draggable_bottom_sheet.dart';
|
||||
export 'draggable_bottom_sheet/draggable_bottom_sheet2.dart';
|
||||
export 'draggable_bottom_sheet/draggable_bottom_sheet_controller.dart';
|
||||
export 'empty_widget.dart';
|
||||
//indicators - unified loading components
|
||||
export 'indicators/core_loading_indicator.dart';
|
||||
//inputs
|
||||
export 'inputs/inputs.dart';
|
||||
//list_item
|
||||
|
||||
Reference in New Issue
Block a user