mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 10:25:28 +03:00
update
This commit is contained in:
@@ -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<void> save(Color primary, Color secondary) async {
|
||||
static Future<void> 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<void> reset(bool isDark) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_key1);
|
||||
await prefs.remove(_key2);
|
||||
await prefs.remove(_keyGradient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,8 +154,9 @@ final recentTransactionsProvider = Provider<List<Transaction>>((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<CardColorsNotifier, CardColors>((ref) {
|
||||
@@ -167,17 +168,25 @@ class CardColorsNotifier extends StateNotifier<CardColors> {
|
||||
: super(const CardColors(
|
||||
CardColorService.defaultPrimary,
|
||||
CardColorService.defaultSecondary,
|
||||
GradientType.linear,
|
||||
)) {
|
||||
_load();
|
||||
}
|
||||
|
||||
Future<void> _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<void> save(Color primary, Color secondary) async {
|
||||
state = CardColors(primary, secondary);
|
||||
await CardColorService.save(primary, secondary);
|
||||
Future<void> save(Color primary, Color secondary, GradientType gradient) async {
|
||||
state = CardColors(primary, secondary, gradient);
|
||||
await CardColorService.save(primary, secondary, gradient);
|
||||
}
|
||||
|
||||
Future<void> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
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<DashboardScreen> {
|
||||
_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<DashboardScreen> {
|
||||
|
||||
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<DashboardScreen> {
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user