diff --git a/lib/core/services/card_color_service.dart b/lib/core/services/card_color_service.dart index f73c1ee..5e76618 100644 --- a/lib/core/services/card_color_service.dart +++ b/lib/core/services/card_color_service.dart @@ -1,27 +1,44 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; +enum GradientType { linear, radial, sweep, linearReverse } + class CardColorService { static const _key1 = 'card_color_primary'; static const _key2 = 'card_color_secondary'; + static const _keyGradient = 'card_gradient_type'; - // defaults match existing gradient: Color(0xFF6B5DD3) and Color(0xFF2A2040) static const defaultPrimary = Color(0xFF6B5DD3); static const defaultSecondary = Color(0xFF2A2040); + static const defaultGradient = GradientType.linear; - static Future<(Color, Color)> load() async { + // light theme defaults + static const defaultPrimaryLight = Color(0xFF7C6DED); + static const defaultSecondaryLight = Color(0xFF3D2E8A); + + static Future<(Color, Color, GradientType)> load() async { final prefs = await SharedPreferences.getInstance(); final c1 = prefs.getInt(_key1); final c2 = prefs.getInt(_key2); + final g = prefs.getInt(_keyGradient); return ( c1 != null ? Color(c1) : defaultPrimary, c2 != null ? Color(c2) : defaultSecondary, + g != null ? GradientType.values[g] : defaultGradient, ); } - static Future save(Color primary, Color secondary) async { + static Future save(Color primary, Color secondary, GradientType gradient) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_key1, primary.value); await prefs.setInt(_key2, secondary.value); + await prefs.setInt(_keyGradient, gradient.index); + } + + static Future reset(bool isDark) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_key1); + await prefs.remove(_key2); + await prefs.remove(_keyGradient); } } diff --git a/lib/features/dashboard/provider.dart b/lib/features/dashboard/provider.dart index a4392e1..3e4902f 100644 --- a/lib/features/dashboard/provider.dart +++ b/lib/features/dashboard/provider.dart @@ -154,8 +154,9 @@ final recentTransactionsProvider = Provider>((ref) { class CardColors { final Color primary; final Color secondary; + final GradientType gradientType; - const CardColors(this.primary, this.secondary); + const CardColors(this.primary, this.secondary, [this.gradientType = GradientType.linear]); } final cardColorsProvider = StateNotifierProvider((ref) { @@ -167,17 +168,25 @@ class CardColorsNotifier extends StateNotifier { : super(const CardColors( CardColorService.defaultPrimary, CardColorService.defaultSecondary, + GradientType.linear, )) { _load(); } Future _load() async { - final (c1, c2) = await CardColorService.load(); - state = CardColors(c1, c2); + final (c1, c2, g) = await CardColorService.load(); + state = CardColors(c1, c2, g); } - Future save(Color primary, Color secondary) async { - state = CardColors(primary, secondary); - await CardColorService.save(primary, secondary); + Future save(Color primary, Color secondary, GradientType gradient) async { + state = CardColors(primary, secondary, gradient); + await CardColorService.save(primary, secondary, gradient); + } + + Future reset(bool isDark) async { + final primary = isDark ? CardColorService.defaultPrimary : CardColorService.defaultPrimaryLight; + final secondary = isDark ? CardColorService.defaultSecondary : CardColorService.defaultSecondaryLight; + state = CardColors(primary, secondary, GradientType.linear); + await CardColorService.save(primary, secondary, GradientType.linear); } } diff --git a/lib/features/dashboard/screen.dart b/lib/features/dashboard/screen.dart index 87bae8a..cff45ea 100644 --- a/lib/features/dashboard/screen.dart +++ b/lib/features/dashboard/screen.dart @@ -53,6 +53,8 @@ class _DashboardScreenState extends ConsumerState { Color _savedSecondary = CardColorService.defaultSecondary; HSVColor _savedPrimaryHSV = HSVColor.fromColor(CardColorService.defaultPrimary); HSVColor _savedSecondaryHSV = HSVColor.fromColor(CardColorService.defaultSecondary); + GradientType _tempGradientType = GradientType.linear; + GradientType _savedGradientType = GradientType.linear; OverlayEntry? _overlayEntry; HSVColor get _currentHSV => _editingPrimary ? _tempPrimaryHSV : _tempSecondaryHSV; @@ -80,10 +82,12 @@ class _DashboardScreenState extends ConsumerState { _savedSecondary = colors.secondary; _savedPrimaryHSV = HSVColor.fromColor(colors.primary); _savedSecondaryHSV = HSVColor.fromColor(colors.secondary); + _savedGradientType = colors.gradientType; _tempPrimary = colors.primary; _tempSecondary = colors.secondary; _tempPrimaryHSV = HSVColor.fromColor(colors.primary); _tempSecondaryHSV = HSVColor.fromColor(colors.secondary); + _tempGradientType = colors.gradientType; setState(() { _editingCard = true; @@ -104,11 +108,12 @@ class _DashboardScreenState extends ConsumerState { void _closeOverlay({required bool apply}) { if (apply) { - ref.read(cardColorsProvider.notifier).save(_tempPrimary, _tempSecondary); + ref.read(cardColorsProvider.notifier).save(_tempPrimary, _tempSecondary, _tempGradientType); } else { setState(() { _tempPrimary = _savedPrimary; _tempSecondary = _savedSecondary; + _tempGradientType = _savedGradientType; }); } _overlayEntry?.remove(); @@ -210,6 +215,7 @@ class _DashboardScreenState extends ConsumerState { onLongPress: _onCardLongPress, previewPrimary: _editingCard ? _tempPrimary : null, previewSecondary: _editingCard ? _tempSecondary : null, + previewGradientType: _editingCard ? _tempGradientType : null, ), const SizedBox(height: 16), _SummaryRow( @@ -630,6 +636,7 @@ class _BalanceCard extends ConsumerStatefulWidget { final VoidCallback? onLongPress; final Color? previewPrimary; final Color? previewSecondary; + final GradientType? previewGradientType; const _BalanceCard({ required this.balance, @@ -637,6 +644,7 @@ class _BalanceCard extends ConsumerStatefulWidget { this.onLongPress, this.previewPrimary, this.previewSecondary, + this.previewGradientType, }); @override @@ -674,6 +682,41 @@ class _BalanceCardState extends ConsumerState<_BalanceCard> super.dispose(); } + Gradient _buildGradient(Color primary, Color secondary, GradientType type) { + final colors = [primary, secondary, Color.lerp(secondary, Colors.black, 0.3)!]; + const stops = [0.0, 0.5, 1.0]; + + switch (type) { + case GradientType.linear: + return LinearGradient( + begin: const Alignment(-0.5, -0.5), + end: const Alignment(0.5, 0.5), + colors: colors, + stops: stops, + ); + case GradientType.linearReverse: + return LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: colors, + stops: stops, + ); + case GradientType.radial: + return RadialGradient( + center: const Alignment(-0.4, -0.4), + radius: 1.2, + colors: colors, + stops: stops, + ); + case GradientType.sweep: + return SweepGradient( + center: const Alignment(-0.4, -0.4), + colors: colors, + stops: stops, + ); + } + } + @override Widget build(BuildContext context) { final rates = ref.read(exchangeRateServiceProvider); @@ -681,6 +724,7 @@ class _BalanceCardState extends ConsumerState<_BalanceCard> final savedColors = ref.watch(cardColorsProvider); final primary = widget.previewPrimary ?? savedColors.primary; final secondary = widget.previewSecondary ?? savedColors.secondary; + final gradientType = widget.previewGradientType ?? savedColors.gradientType; final allCurrencies = [ ('USD', r'$'), ('EUR', '€'), @@ -710,16 +754,7 @@ class _BalanceCardState extends ConsumerState<_BalanceCard> height: 180, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), - gradient: LinearGradient( - begin: const Alignment(-0.5, -0.5), - end: const Alignment(0.5, 0.5), - colors: [ - primary, - secondary, - Color.lerp(secondary, Colors.black, 0.3)!, - ], - stops: const [0.0, 0.5, 1.0], - ), + gradient: _buildGradient(primary, secondary, gradientType), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.4), @@ -1102,7 +1137,7 @@ class _FullScreenBlurOverlayState extends State<_FullScreenBlurOverlay> { final mq = MediaQuery.of(widget.context); final cardTop = mq.padding.top + kToolbarHeight + 16; final cardHeight = 180.0; - final panelTop = cardTop + cardHeight + 78; + final panelTop = cardTop + cardHeight + 50; final panelBottom = mq.padding.bottom + 16; return Material( @@ -1140,6 +1175,7 @@ class _FullScreenBlurOverlayState extends State<_FullScreenBlurOverlay> { onLongPress: null, previewPrimary: dash._tempPrimary, previewSecondary: dash._tempSecondary, + previewGradientType: dash._tempGradientType, ), ), ), @@ -1364,25 +1400,144 @@ class _FullScreenBlurOverlayState extends State<_FullScreenBlurOverlay> { ], ), const SizedBox(height: 16), - // APPLY BUTTON - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () => dash._closeOverlay(apply: true), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF7C6DED), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), + // GRADIENT TYPE SECTION + Text( + 'Gradient Style', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.6), + ), + ), + const SizedBox(height: 10), + Row( + children: GradientType.values.map((type) { + final isSelected = dash._tempGradientType == type; + final label = switch (type) { + GradientType.linear => 'Linear', + GradientType.linearReverse => 'Reverse', + GradientType.radial => 'Radial', + GradientType.sweep => 'Sweep', + }; + final icon = switch (type) { + GradientType.linear => Icons.trending_flat_rounded, + GradientType.linearReverse => Icons.swap_horiz_rounded, + GradientType.radial => Icons.blur_circular_rounded, + GradientType.sweep => Icons.rotate_right_rounded, + }; + return Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 8), + child: GestureDetector( + onTap: () { + dash.setState(() => dash._tempGradientType = type); + setPanelState(() {}); + dash._overlayEntry?.markNeedsBuild(); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: isSelected + ? const Color(0xFF7C6DED).withOpacity(0.15) + : Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.05), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: isSelected + ? const Color(0xFF7C6DED) + : Colors.transparent, + width: 1.5, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + size: 18, + color: isSelected + ? const Color(0xFF7C6DED) + : Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.5), + ), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + fontSize: 10, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + color: isSelected + ? const Color(0xFF7C6DED) + : Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.5), + ), + ), + ], + ), + ), + ), + ), + ); + }).toList(), + ), + const SizedBox(height: 16), + // RESET + APPLY ROW + Row( + children: [ + // RESET button + Expanded( + child: OutlinedButton.icon( + onPressed: () { + final isDark = Theme.of(widget.context).brightness == Brightness.dark; + final defPrimary = isDark + ? CardColorService.defaultPrimary + : CardColorService.defaultPrimaryLight; + final defSecondary = isDark + ? CardColorService.defaultSecondary + : CardColorService.defaultSecondaryLight; + dash.setState(() { + dash._tempPrimary = defPrimary; + dash._tempSecondary = defSecondary; + dash._tempPrimaryHSV = HSVColor.fromColor(defPrimary); + dash._tempSecondaryHSV = HSVColor.fromColor(defSecondary); + dash._tempGradientType = GradientType.linear; + }); + setPanelState(() {}); + dash._overlayEntry?.markNeedsBuild(); + }, + icon: const Icon(Icons.restart_alt_rounded, size: 16), + label: const Text('Reset'), + style: OutlinedButton.styleFrom( + foregroundColor: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.7), + side: BorderSide( + color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.2), + ), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), ), ), - child: const Text( - 'Apply', - style: - TextStyle(fontWeight: FontWeight.w700, fontSize: 15), + const SizedBox(width: 12), + // APPLY button + Expanded( + flex: 2, + child: ElevatedButton( + onPressed: () => dash._closeOverlay(apply: true), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF7C6DED), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + child: const Text( + 'Apply', + style: TextStyle(fontWeight: FontWeight.w700, fontSize: 15), + ), + ), ), - ), + ], ), ], );