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,43 @@
import 'package:flutter/material.dart';
/// Bottom navigation bar for the app
class AppBottomNav extends StatelessWidget {
final int currentIndex;
final Function(int) onTap;
const AppBottomNav({
super.key,
required this.currentIndex,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return NavigationBar(
selectedIndex: currentIndex,
onDestinationSelected: onTap,
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.inventory_2_outlined),
selectedIcon: Icon(Icons.inventory_2),
label: 'Products',
),
NavigationDestination(
icon: Icon(Icons.category_outlined),
selectedIcon: Icon(Icons.category),
label: 'Categories',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: 'Settings',
),
],
);
}
}

View File

@@ -0,0 +1,220 @@
import 'package:flutter/material.dart';
/// A Material 3 badge widget
class BadgeWidget extends StatelessWidget {
final Widget child;
final String? label;
final int? count;
final Color? backgroundColor;
final Color? textColor;
final Alignment alignment;
final bool show;
const BadgeWidget({
super.key,
required this.child,
this.label,
this.count,
this.backgroundColor,
this.textColor,
this.alignment = Alignment.topRight,
this.show = true,
});
@override
Widget build(BuildContext context) {
if (!show) return child;
return Badge(
label: _buildLabel(context),
alignment: alignment,
backgroundColor: backgroundColor,
textColor: textColor,
child: child,
);
}
Widget? _buildLabel(BuildContext context) {
if (count != null) {
return Text(count! > 99 ? '99+' : '$count');
}
if (label != null) {
return Text(label!);
}
return null;
}
}
/// A status badge for products
class StatusBadge extends StatelessWidget {
final String label;
final StatusBadgeType type;
final IconData? icon;
const StatusBadge({
super.key,
required this.label,
required this.type,
this.icon,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final Color backgroundColor;
final Color textColor;
switch (type) {
case StatusBadgeType.success:
backgroundColor = const Color(0xFF4CAF50);
textColor = Colors.white;
break;
case StatusBadgeType.warning:
backgroundColor = Colors.orange;
textColor = Colors.white;
break;
case StatusBadgeType.error:
backgroundColor = colorScheme.error;
textColor = colorScheme.onError;
break;
case StatusBadgeType.info:
backgroundColor = colorScheme.primaryContainer;
textColor = colorScheme.onPrimaryContainer;
break;
case StatusBadgeType.neutral:
backgroundColor = colorScheme.surfaceContainerHighest;
textColor = colorScheme.onSurface;
break;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(
icon,
size: 14,
color: textColor,
),
const SizedBox(width: 4),
],
Text(
label,
style: theme.textTheme.labelSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
}
enum StatusBadgeType {
success,
warning,
error,
info,
neutral,
}
/// A count badge for displaying numbers
class CountBadge extends StatelessWidget {
final int count;
final Color? backgroundColor;
final Color? textColor;
final double size;
const CountBadge({
super.key,
required this.count,
this.backgroundColor,
this.textColor,
this.size = 20,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Container(
constraints: BoxConstraints(
minWidth: size,
minHeight: size,
),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: backgroundColor ?? colorScheme.primary,
shape: BoxShape.circle,
),
child: Center(
child: Text(
count > 99 ? '99+' : '$count',
style: theme.textTheme.labelSmall?.copyWith(
color: textColor ?? colorScheme.onPrimary,
fontWeight: FontWeight.bold,
fontSize: size * 0.4,
),
textAlign: TextAlign.center,
),
),
);
}
}
/// A notification badge (simple dot)
class NotificationBadge extends StatelessWidget {
final Widget child;
final bool show;
final Color? color;
final double size;
const NotificationBadge({
super.key,
required this.child,
this.show = true,
this.color,
this.size = 8,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
if (!show) return child;
return Stack(
clipBehavior: Clip.none,
children: [
child,
Positioned(
right: 0,
top: 0,
child: Container(
width: size,
height: size,
decoration: BoxDecoration(
color: color ?? colorScheme.error,
shape: BoxShape.circle,
border: Border.all(
color: theme.scaffoldBackgroundColor,
width: 1.5,
),
),
),
),
],
);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
/// Custom app bar widget
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title;
final List<Widget>? actions;
final Widget? leading;
final PreferredSizeWidget? bottom;
const CustomAppBar({
super.key,
required this.title,
this.actions,
this.leading,
this.bottom,
});
@override
Widget build(BuildContext context) {
return AppBar(
title: Text(title),
leading: leading,
actions: actions,
bottom: bottom,
);
}
@override
Size get preferredSize => Size.fromHeight(
kToolbarHeight + (bottom?.preferredSize.height ?? 0.0),
);
}

View File

@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import '../../core/utils/formatters.dart';
/// Widget to display formatted price
class PriceDisplay extends StatelessWidget {
final double price;
final TextStyle? style;
final String currency;
const PriceDisplay({
super.key,
required this.price,
this.style,
this.currency = 'USD',
});
@override
Widget build(BuildContext context) {
return Text(
Formatters.formatPrice(price, currency: currency),
style: style ?? Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
);
}
}

View File

@@ -0,0 +1,7 @@
// Core Widgets
export 'price_display.dart';
export 'app_bottom_nav.dart';
export 'custom_app_bar.dart';
export 'badge_widget.dart';
// This file provides a central export point for all shared widgets