feat: add more icons and color selection for categories
This commit is contained in:
@@ -5,6 +5,8 @@ import '../models/category.dart'; // Import the Category model for return types
|
||||
class CategoryUtils {
|
||||
// Private map storing details for each predefined category name.
|
||||
// Using a record `({IconData icon, Color color})` for concise structure.
|
||||
// This map is mainly for initial data and potentially fallback display logic.
|
||||
// The actual icon/color for user-created categories will come from the database.
|
||||
static final Map<String, ({IconData iconCode, Color colorCode})> _categoryDetails = {
|
||||
'Groceries': (iconCode: Icons.shopping_cart_outlined, colorCode: Colors.green.shade400),
|
||||
'Subscriptions': (iconCode: Icons.subscriptions_outlined, colorCode: Colors.orange.shade400),
|
||||
@@ -12,65 +14,55 @@ class CategoryUtils {
|
||||
'Shopping': (iconCode: Icons.shopping_bag_outlined, colorCode: Colors.blue.shade400),
|
||||
'Transport': (iconCode: Icons.directions_bus_filled_outlined, colorCode: Colors.purple.shade400),
|
||||
'Travel': (iconCode: Icons.flight_takeoff_outlined, colorCode: Colors.cyan.shade400),
|
||||
'Utilities': (iconCode: Icons.lightbulb_outline, colorCode: Colors.yellow.shade700), // Changed from home_outlined
|
||||
'Utilities': (iconCode: Icons.lightbulb_outline, colorCode: Colors.yellow.shade700),
|
||||
'Health': (iconCode: Icons.local_hospital_outlined, colorCode: Colors.pink.shade300),
|
||||
'Entertainment': (iconCode: Icons.movie_filter_outlined, colorCode: Colors.teal.shade400),
|
||||
'Income': (iconCode: Icons.attach_money, colorCode: Colors.lightGreenAccent.shade700), // Added Income details
|
||||
'Other': (iconCode: Icons.category_outlined, colorCode: Colors.grey.shade500), // Default/fallback
|
||||
// Add placeholder icons used elsewhere
|
||||
'Income': (iconCode: Icons.attach_money, colorCode: Colors.lightGreenAccent.shade700),
|
||||
'Other': (iconCode: Icons.category_outlined, colorCode: Colors.grey.shade500),
|
||||
// Placeholders should ideally not be needed here if DB provides data
|
||||
'label_outline': (iconCode: Icons.label_outline, colorCode: Colors.grey.shade400),
|
||||
'help_outline': (iconCode: Icons.help_outline, colorCode: Colors.grey.shade500),
|
||||
};
|
||||
|
||||
// Default details to return if a category name is not found in the map.
|
||||
// Default details to return if a category name is not found (e.g., deleted category).
|
||||
static final _defaultDetails = (iconCode: Icons.category_outlined, colorCode: Colors.grey.shade500);
|
||||
|
||||
/// Returns the IconData and Color associated with a given category name.
|
||||
///
|
||||
/// If the `categoryName` is found in the predefined map, its details are returned.
|
||||
/// Otherwise, default icon and color are returned.
|
||||
/// Returns the IconData and Color associated with a given category name (primarily for fallback).
|
||||
/// It's better to rely on data fetched directly from the database (CategoryDb).
|
||||
static ({Color colorCode, IconData iconCode}) getCategoryDetails(String categoryName) {
|
||||
// Use the null-aware operator `??` to provide default values if the key doesn't exist.
|
||||
return _categoryDetails[categoryName] ?? _defaultDetails;
|
||||
}
|
||||
|
||||
/// Returns a `Category` object based on its name and amount.
|
||||
///
|
||||
/// This is useful when you have the name and amount (e.g., from database aggregation)
|
||||
/// and need to construct a full `Category` object including the icon and color.
|
||||
/// Returns a `Category` object based on its name and amount (primarily for UI models like Pie Chart).
|
||||
/// Fetches icon/color details using `getCategoryDetails` as a fallback mechanism.
|
||||
static Category getCategoryByName(String name, double amount) {
|
||||
// Retrieve the icon and color using the getCategoryDetails method.
|
||||
final details = getCategoryDetails(name);
|
||||
// Construct and return the Category object.
|
||||
return Category(name, amount, details.colorCode, details.iconCode);
|
||||
}
|
||||
|
||||
/// Returns a list of all predefined category names (excluding placeholders).
|
||||
///
|
||||
/// Useful for generating UI elements like filter chips or dropdowns.
|
||||
/// Might be less useful now that categories are fully dynamic.
|
||||
static List<String> getAllCategoryNames() {
|
||||
// Return the keys from the details map as a list, excluding known placeholders
|
||||
return _categoryDetails.keys.where((key) => key != 'label_outline' && key != 'help_outline').toList();
|
||||
}
|
||||
|
||||
/// Converts a string icon name (like 'shopping_cart_outlined') to IconData.
|
||||
///
|
||||
/// This map should contain mappings for all icon names stored in the database.
|
||||
/// This map is crucial for displaying icons based on the string stored in the DB.
|
||||
static final Map<String, IconData> _stringToIconData = {
|
||||
// Existing Icons
|
||||
'shopping_cart_outlined': Icons.shopping_cart_outlined,
|
||||
'subscriptions_outlined': Icons.subscriptions_outlined,
|
||||
'restaurant_menu_outlined': Icons.restaurant_menu_outlined,
|
||||
'shopping_bag_outlined': Icons.shopping_bag_outlined,
|
||||
'directions_bus_filled_outlined': Icons.directions_bus_filled_outlined,
|
||||
'flight_takeoff_outlined': Icons.flight_takeoff_outlined,
|
||||
'lightbulb_outline': Icons.lightbulb_outline, // Updated from home_outlined
|
||||
'lightbulb_outline': Icons.lightbulb_outline,
|
||||
'local_hospital_outlined': Icons.local_hospital_outlined,
|
||||
'movie_filter_outlined': Icons.movie_filter_outlined,
|
||||
'attach_money': Icons.attach_money, // For Income
|
||||
'category_outlined': Icons.category_outlined, // For Other/Default
|
||||
'label_outline': Icons.label_outline, // Placeholder used in AddCategoryDialog
|
||||
'help_outline': Icons.help_outline, // Placeholder for missing categories in list
|
||||
// Add more icons suitable for categories
|
||||
'attach_money': Icons.attach_money, // Income
|
||||
'category_outlined': Icons.category_outlined, // Other/Default
|
||||
'label_outline': Icons.label_outline, // Placeholder
|
||||
'help_outline': Icons.help_outline, // Placeholder
|
||||
'home_outlined': Icons.home_outlined,
|
||||
'pets_outlined': Icons.pets_outlined,
|
||||
'school_outlined': Icons.school_outlined,
|
||||
@@ -93,10 +85,108 @@ class CategoryUtils {
|
||||
'park_outlined': Icons.park_outlined, // Parks/Outdoors
|
||||
'science_outlined': Icons.science_outlined, // Science/Tech
|
||||
'palette_outlined': Icons.palette_outlined, // Art/Design
|
||||
|
||||
// Added More Icons
|
||||
'account_balance_outlined': Icons.account_balance_outlined, // Bank/Finance
|
||||
'analytics_outlined': Icons.analytics_outlined, // Analysis/Reports
|
||||
'apartment_outlined': Icons.apartment_outlined, // Rent/Mortgage
|
||||
'beach_access_outlined': Icons.beach_access_outlined, // Vacation
|
||||
'brush_outlined': Icons.brush_outlined, // Personal Care/Cosmetics
|
||||
'business_center_outlined': Icons.business_center_outlined, // Business Expenses
|
||||
'call_outlined': Icons.call_outlined, // Phone Bill
|
||||
'camera_alt_outlined': Icons.camera_alt_outlined, // Photography/Equipment
|
||||
'car_rental_outlined': Icons.car_rental_outlined, // Car Rental
|
||||
'car_repair_outlined': Icons.car_repair_outlined, // Car Repair
|
||||
'celebration_outlined': Icons.celebration_outlined, // Party/Events
|
||||
'computer_outlined': Icons.computer_outlined, // Computer/Software
|
||||
'construction_outlined': Icons.construction_outlined, // Home Improvement
|
||||
'cottage_outlined': Icons.cottage_outlined, // Vacation Home
|
||||
'delivery_dining_outlined': Icons.delivery_dining_outlined, // Food Delivery
|
||||
'diamond_outlined': Icons.diamond_outlined, // Jewelry/Luxury
|
||||
'dry_cleaning_outlined': Icons.dry_cleaning_outlined, // Laundry/Dry Cleaning
|
||||
'electrical_services_outlined': Icons.electrical_services_outlined, // Electrician
|
||||
'emoji_events_outlined': Icons.emoji_events_outlined, // Awards/Competitions
|
||||
'fastfood_outlined': Icons.fastfood_outlined, // Fast Food
|
||||
'fax_outlined': Icons.fax_outlined, // Office Supplies
|
||||
'festival_outlined': Icons.festival_outlined, // Festivals/Events
|
||||
'fireplace_outlined': Icons.fireplace_outlined, // Heating/Fuel
|
||||
'flatware_outlined': Icons.flatware_outlined, // Kitchenware
|
||||
'gas_meter_outlined': Icons.gas_meter_outlined, // Gas Bill
|
||||
'gavel_outlined': Icons.gavel_outlined, // Legal Fees
|
||||
'grass_outlined': Icons.grass_outlined, // Gardening/Lawn Care
|
||||
'hardware_outlined': Icons.hardware_outlined, // Hardware Store
|
||||
'hearing_outlined': Icons.hearing_outlined, // Audio/Headphones
|
||||
'hiking_outlined': Icons.hiking_outlined, // Hiking/Outdoor Gear
|
||||
'icecream_outlined': Icons.icecream_outlined, // Ice Cream/Desserts
|
||||
'interests_outlined': Icons.interests_outlined, // Hobbies General
|
||||
'key_outlined': Icons.key_outlined, // Keys/Locks
|
||||
'liquor_outlined': Icons.liquor_outlined, // Alcohol
|
||||
'local_activity_outlined': Icons.local_activity_outlined, // Tickets/Events
|
||||
'local_atm_outlined': Icons.local_atm_outlined, // ATM Withdrawal
|
||||
'local_convenience_store_outlined': Icons.local_convenience_store_outlined, // Convenience Store
|
||||
'local_florist_outlined': Icons.local_florist_outlined, // Flowers
|
||||
'local_gas_station_outlined': Icons.local_gas_station_outlined, // Gas/Fuel
|
||||
'local_laundry_service_outlined': Icons.local_laundry_service_outlined, // Laundry Service
|
||||
'local_mall_outlined': Icons.local_mall_outlined, // Mall Shopping
|
||||
'local_offer_outlined': Icons.local_offer_outlined, // Discounts/Sales
|
||||
'local_parking_outlined': Icons.local_parking_outlined, // Parking Fees
|
||||
'local_pharmacy_outlined': Icons.local_pharmacy_outlined, // Pharmacy
|
||||
'local_pizza_outlined': Icons.local_pizza_outlined, // Pizza
|
||||
'local_shipping_outlined': Icons.local_shipping_outlined, // Shipping Costs
|
||||
'local_taxi_outlined': Icons.local_taxi_outlined, // Taxi/Rideshare
|
||||
'lunch_dining_outlined': Icons.lunch_dining_outlined, // Lunch
|
||||
'medication_outlined': Icons.medication_outlined, // Medication
|
||||
'museum_outlined': Icons.museum_outlined, // Museum/Exhibits
|
||||
'newspaper_outlined': Icons.newspaper_outlined, // Newspapers/Magazines
|
||||
'paid_outlined': Icons.paid_outlined, // Payments/Transfers
|
||||
'pedal_bike_outlined': Icons.pedal_bike_outlined, // Cycling
|
||||
'plumbing_outlined': Icons.plumbing_outlined, // Plumber
|
||||
'ramen_dining_outlined': Icons.ramen_dining_outlined, // Noodles/Asian Food
|
||||
'recycling_outlined': Icons.recycling_outlined, // Recycling Fees
|
||||
'redeem_outlined': Icons.redeem_outlined, // Gifts Received/Redeemed
|
||||
'request_quote_outlined': Icons.request_quote_outlined, // Invoices/Quotes
|
||||
'roller_skating_outlined': Icons.roller_skating_outlined, // Skating
|
||||
'roofing_outlined': Icons.roofing_outlined, // Roofing Repair
|
||||
'room_service_outlined': Icons.room_service_outlined, // Hotel Service
|
||||
'settop_component_outlined': Icons.settop_component_outlined, // Cable/Streaming Box
|
||||
'shield_outlined': Icons.shield_outlined, // Insurance
|
||||
'skateboarding_outlined': Icons.skateboarding_outlined, // Skateboarding
|
||||
'smoking_rooms_outlined': Icons.smoking_rooms_outlined, // Tobacco
|
||||
'spa_outlined': Icons.spa_outlined, // Spa/Wellness
|
||||
'sports_bar_outlined': Icons.sports_bar_outlined, // Sports Bar
|
||||
'sports_basketball_outlined': Icons.sports_basketball_outlined, // Basketball
|
||||
'sports_football_outlined': Icons.sports_football_outlined, // Football
|
||||
'sports_golf_outlined': Icons.sports_golf_outlined, // Golf
|
||||
'sports_gymnastics_outlined': Icons.sports_gymnastics_outlined, // Gymnastics
|
||||
'sports_handball_outlined': Icons.sports_handball_outlined, // Handball
|
||||
'sports_hockey_outlined': Icons.sports_hockey_outlined, // Hockey
|
||||
'sports_kabaddi_outlined': Icons.sports_kabaddi_outlined, // Kabaddi
|
||||
'sports_mma_outlined': Icons.sports_mma_outlined, // MMA
|
||||
'sports_motorsports_outlined': Icons.sports_motorsports_outlined, // Motorsports
|
||||
'sports_soccer_outlined': Icons.sports_soccer_outlined, // Soccer
|
||||
'sports_tennis_outlined': Icons.sports_tennis_outlined, // Tennis
|
||||
'sports_volleyball_outlined': Icons.sports_volleyball_outlined, // Volleyball
|
||||
'stadium_outlined': Icons.stadium_outlined, // Stadium/Events
|
||||
'store_mall_directory_outlined': Icons.store_mall_directory_outlined, // Department Store
|
||||
'stroller_outlined': Icons.stroller_outlined, // Baby Supplies
|
||||
'subway_outlined': Icons.subway_outlined, // Subway/Metro
|
||||
'surfing_outlined': Icons.surfing_outlined, // Surfing
|
||||
'sync_alt_outlined': Icons.sync_alt_outlined, // Transfers
|
||||
'theater_comedy_outlined': Icons.theater_comedy_outlined, // Comedy Club
|
||||
'theaters_outlined': Icons.theaters_outlined, // Cinema/Theater
|
||||
'toys_outlined': Icons.toys_outlined, // Toys
|
||||
'train_outlined': Icons.train_outlined, // Train Travel
|
||||
'tram_outlined': Icons.tram_outlined, // Tram
|
||||
'two_wheeler_outlined': Icons.two_wheeler_outlined, // Motorcycle/Scooter
|
||||
'vape_free_outlined': Icons.vape_free_outlined, // Vaping (quit)
|
||||
'vaping_rooms_outlined': Icons.vaping_rooms_outlined, // Vaping
|
||||
'videogame_asset_outlined': Icons.videogame_asset_outlined, // Video Games
|
||||
'water_drop_outlined': Icons.water_drop_outlined, // Water Bill
|
||||
'wifi_outlined': Icons.wifi_outlined, // Internet Bill
|
||||
'wine_bar_outlined': Icons.wine_bar_outlined, // Wine Bar
|
||||
};
|
||||
|
||||
/// Returns the IconData corresponding to the given icon name string.
|
||||
///
|
||||
/// If the `iconName` is not found, returns a default fallback icon (`Icons.help_outline`).
|
||||
static IconData getIconFromString(String iconName) {
|
||||
return _stringToIconData[iconName] ?? Icons.help_outline; // Return default if not found
|
||||
@@ -105,11 +195,26 @@ class CategoryUtils {
|
||||
/// Returns a map of available icons for selection.
|
||||
/// Excludes placeholder/internal icons.
|
||||
static Map<String, IconData> getAvailableIcons() {
|
||||
// Create a new map excluding specific keys
|
||||
final availableIcons = Map<String, IconData>.from(_stringToIconData);
|
||||
availableIcons.remove('help_outline'); // Remove fallback icon
|
||||
availableIcons.remove('label_outline'); // Remove placeholder icon
|
||||
// Add more exclusions if needed
|
||||
availableIcons.remove('attach_money'); // Remove income icon (usually not selectable for expense)
|
||||
availableIcons.remove('category_outlined'); // Remove generic 'Other' icon
|
||||
// Add more exclusions if needed (e.g., icons only relevant for income)
|
||||
return availableIcons;
|
||||
}
|
||||
|
||||
/// List of available colors for category selection.
|
||||
static const List<Color> availableColors = [
|
||||
Colors.red, Colors.pink, Colors.purple, Colors.deepPurple,
|
||||
Colors.indigo, Colors.blue, Colors.lightBlue, Colors.cyan,
|
||||
Colors.teal, Colors.green, Colors.lightGreen, Colors.lime,
|
||||
Colors.yellow, Colors.amber, Colors.orange, Colors.deepOrange,
|
||||
Colors.brown, Colors.grey, Colors.blueGrey,
|
||||
// Adding some accent colors
|
||||
Colors.redAccent, Colors.pinkAccent, Colors.purpleAccent,
|
||||
Colors.indigoAccent, Colors.blueAccent, Colors.cyanAccent,
|
||||
Colors.tealAccent, Colors.greenAccent, Colors.limeAccent,
|
||||
Colors.yellowAccent, Colors.amberAccent, Colors.orangeAccent,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ class _AddCategoryDialogState extends State<AddCategoryDialog> {
|
||||
// State for selected icon
|
||||
String _selectedIconName = 'label_outline'; // Default icon name (string)
|
||||
IconData _selectedIconData = Icons.label_outline; // Default icon data
|
||||
// TODO: Add state for selected color later
|
||||
// State for selected color
|
||||
Color _selectedColor = CategoryUtils.availableColors[9]; // Default to green
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -56,19 +57,22 @@ class _AddCategoryDialogState extends State<AddCategoryDialog> {
|
||||
mainAxisSpacing: 10.0,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final bool isSelected = _selectedIconName == iconNames[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(context, iconNames[index]); // Return the selected icon name (String)
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8.0), // Ripple effect matches border
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _selectedIconName == iconNames[index]
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.primary // Highlight selected
|
||||
: Colors.transparent,
|
||||
width: 2.0,
|
||||
: Colors.grey.withOpacity(0.3), // Subtle border for all
|
||||
width: isSelected ? 2.0 : 1.0, // Thicker border if selected
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
color: isSelected ? Theme.of(context).colorScheme.primary.withOpacity(0.1) : null,
|
||||
),
|
||||
child: Icon(
|
||||
iconDatas[index],
|
||||
@@ -108,17 +112,17 @@ class _AddCategoryDialogState extends State<AddCategoryDialog> {
|
||||
final name = _nameController.text;
|
||||
// Use the selected icon name
|
||||
final icon = _selectedIconName;
|
||||
// TODO: Get selected color
|
||||
final color = Colors.grey.shade400.value; // Placeholder color
|
||||
// Use the selected color value
|
||||
final color = _selectedColor.value;
|
||||
|
||||
final newCategoryCompanion = db.CategoriesCompanion(
|
||||
name: Value(name),
|
||||
icon: Value(icon), // Use selected icon name
|
||||
color: Value(color), // Use placeholder color
|
||||
color: Value(color), // Use selected color value
|
||||
);
|
||||
|
||||
try {
|
||||
// TODO: Add check for duplicate category name before inserting
|
||||
// TODO: Add check for duplicate category name before inserting (more robustly)
|
||||
final newCategoryId = await widget.database.addCategory(newCategoryCompanion);
|
||||
final newCategory = await widget.database.getCategoryById(newCategoryId);
|
||||
if (mounted) {
|
||||
@@ -127,7 +131,7 @@ class _AddCategoryDialogState extends State<AddCategoryDialog> {
|
||||
} catch (e) {
|
||||
print('Error adding category: $e');
|
||||
// Handle potential duplicate name error from database (depends on DB constraints)
|
||||
String errorMessage = 'Error adding category: $e';
|
||||
String errorMessage = 'Произошла ошибка при добавлении категории.';
|
||||
if (e.toString().toLowerCase().contains('unique constraint failed')) {
|
||||
errorMessage = 'Категория с таким именем уже существует.';
|
||||
}
|
||||
@@ -146,13 +150,20 @@ class _AddCategoryDialogState extends State<AddCategoryDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
// Determine icon color based on the selected background color's brightness
|
||||
final iconColor = ThemeData.estimateBrightnessForColor(_selectedColor) == Brightness.dark
|
||||
? Colors.white70
|
||||
: Colors.black87;
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Создать категорию'),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start, // Align children to the start
|
||||
children: [
|
||||
// --- Icon and Name Row ---
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
@@ -161,12 +172,13 @@ class _AddCategoryDialogState extends State<AddCategoryDialog> {
|
||||
padding: const EdgeInsets.only(right: 16.0, bottom: 10.0), // Add padding
|
||||
child: InkWell(
|
||||
onTap: _showIconPicker, // Show picker on tap
|
||||
customBorder: const CircleBorder(), // Make ripple circular
|
||||
child: CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: theme.colorScheme.primaryContainer.withOpacity(0.5),
|
||||
backgroundColor: _selectedColor, // Use selected color for background
|
||||
child: Icon(
|
||||
_selectedIconData, // Display selected icon
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
color: iconColor, // Adjust icon color based on background
|
||||
size: 26,
|
||||
),
|
||||
),
|
||||
@@ -178,13 +190,15 @@ class _AddCategoryDialogState extends State<AddCategoryDialog> {
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Название категории',
|
||||
// Removed icon: Icon(Icons.label_outline), // Icon is now separate
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Пожалуйста, введите название';
|
||||
}
|
||||
// TODO: Add more robust validation (e.g., check against existing names via stream/future)
|
||||
// Basic length check
|
||||
if (value.length > 50) {
|
||||
return 'Название слишком длинное';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
@@ -192,8 +206,48 @@ class _AddCategoryDialogState extends State<AddCategoryDialog> {
|
||||
),
|
||||
],
|
||||
),
|
||||
// TODO: Add Color Picker here (e.g., below the name field)
|
||||
const SizedBox(height: 16), // Add some space
|
||||
const SizedBox(height: 20), // Space before color picker
|
||||
|
||||
// --- Color Picker ---
|
||||
const Text('Выберите цвет:', style: TextStyle(fontSize: 16)),
|
||||
const SizedBox(height: 8),
|
||||
Wrap( // Use Wrap for horizontal layout with wrapping
|
||||
spacing: 8.0, // Horizontal space between circles
|
||||
runSpacing: 8.0, // Vertical space between rows
|
||||
children: CategoryUtils.availableColors.map((color) {
|
||||
final bool isSelected = _selectedColor == color;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedColor = color; // Update selected color on tap
|
||||
});
|
||||
},
|
||||
customBorder: const CircleBorder(),
|
||||
child: Container(
|
||||
width: 32, // Size of the color circle
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? theme.colorScheme.onSurface // Highlight selected
|
||||
: theme.dividerColor, // Border for others
|
||||
width: isSelected ? 3.0 : 1.0,
|
||||
),
|
||||
boxShadow: isSelected ? [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
)
|
||||
] : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 16), // Add some space at the bottom
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -210,3 +264,4 @@ class _AddCategoryDialogState extends State<AddCategoryDialog> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user