mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 18:35:28 +03:00
update
This commit is contained in:
@@ -1,27 +1,44 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
enum GradientType { linear, radial, sweep, linearReverse }
|
||||||
|
|
||||||
class CardColorService {
|
class CardColorService {
|
||||||
static const _key1 = 'card_color_primary';
|
static const _key1 = 'card_color_primary';
|
||||||
static const _key2 = 'card_color_secondary';
|
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 defaultPrimary = Color(0xFF6B5DD3);
|
||||||
static const defaultSecondary = Color(0xFF2A2040);
|
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 prefs = await SharedPreferences.getInstance();
|
||||||
final c1 = prefs.getInt(_key1);
|
final c1 = prefs.getInt(_key1);
|
||||||
final c2 = prefs.getInt(_key2);
|
final c2 = prefs.getInt(_key2);
|
||||||
|
final g = prefs.getInt(_keyGradient);
|
||||||
return (
|
return (
|
||||||
c1 != null ? Color(c1) : defaultPrimary,
|
c1 != null ? Color(c1) : defaultPrimary,
|
||||||
c2 != null ? Color(c2) : defaultSecondary,
|
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();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setInt(_key1, primary.value);
|
await prefs.setInt(_key1, primary.value);
|
||||||
await prefs.setInt(_key2, secondary.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 {
|
class CardColors {
|
||||||
final Color primary;
|
final Color primary;
|
||||||
final Color secondary;
|
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) {
|
final cardColorsProvider = StateNotifierProvider<CardColorsNotifier, CardColors>((ref) {
|
||||||
@@ -167,17 +168,25 @@ class CardColorsNotifier extends StateNotifier<CardColors> {
|
|||||||
: super(const CardColors(
|
: super(const CardColors(
|
||||||
CardColorService.defaultPrimary,
|
CardColorService.defaultPrimary,
|
||||||
CardColorService.defaultSecondary,
|
CardColorService.defaultSecondary,
|
||||||
|
GradientType.linear,
|
||||||
)) {
|
)) {
|
||||||
_load();
|
_load();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _load() async {
|
Future<void> _load() async {
|
||||||
final (c1, c2) = await CardColorService.load();
|
final (c1, c2, g) = await CardColorService.load();
|
||||||
state = CardColors(c1, c2);
|
state = CardColors(c1, c2, g);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> save(Color primary, Color secondary) async {
|
Future<void> save(Color primary, Color secondary, GradientType gradient) async {
|
||||||
state = CardColors(primary, secondary);
|
state = CardColors(primary, secondary, gradient);
|
||||||
await CardColorService.save(primary, secondary);
|
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;
|
Color _savedSecondary = CardColorService.defaultSecondary;
|
||||||
HSVColor _savedPrimaryHSV = HSVColor.fromColor(CardColorService.defaultPrimary);
|
HSVColor _savedPrimaryHSV = HSVColor.fromColor(CardColorService.defaultPrimary);
|
||||||
HSVColor _savedSecondaryHSV = HSVColor.fromColor(CardColorService.defaultSecondary);
|
HSVColor _savedSecondaryHSV = HSVColor.fromColor(CardColorService.defaultSecondary);
|
||||||
|
GradientType _tempGradientType = GradientType.linear;
|
||||||
|
GradientType _savedGradientType = GradientType.linear;
|
||||||
OverlayEntry? _overlayEntry;
|
OverlayEntry? _overlayEntry;
|
||||||
|
|
||||||
HSVColor get _currentHSV => _editingPrimary ? _tempPrimaryHSV : _tempSecondaryHSV;
|
HSVColor get _currentHSV => _editingPrimary ? _tempPrimaryHSV : _tempSecondaryHSV;
|
||||||
@@ -80,10 +82,12 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|||||||
_savedSecondary = colors.secondary;
|
_savedSecondary = colors.secondary;
|
||||||
_savedPrimaryHSV = HSVColor.fromColor(colors.primary);
|
_savedPrimaryHSV = HSVColor.fromColor(colors.primary);
|
||||||
_savedSecondaryHSV = HSVColor.fromColor(colors.secondary);
|
_savedSecondaryHSV = HSVColor.fromColor(colors.secondary);
|
||||||
|
_savedGradientType = colors.gradientType;
|
||||||
_tempPrimary = colors.primary;
|
_tempPrimary = colors.primary;
|
||||||
_tempSecondary = colors.secondary;
|
_tempSecondary = colors.secondary;
|
||||||
_tempPrimaryHSV = HSVColor.fromColor(colors.primary);
|
_tempPrimaryHSV = HSVColor.fromColor(colors.primary);
|
||||||
_tempSecondaryHSV = HSVColor.fromColor(colors.secondary);
|
_tempSecondaryHSV = HSVColor.fromColor(colors.secondary);
|
||||||
|
_tempGradientType = colors.gradientType;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_editingCard = true;
|
_editingCard = true;
|
||||||
@@ -104,11 +108,12 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|||||||
|
|
||||||
void _closeOverlay({required bool apply}) {
|
void _closeOverlay({required bool apply}) {
|
||||||
if (apply) {
|
if (apply) {
|
||||||
ref.read(cardColorsProvider.notifier).save(_tempPrimary, _tempSecondary);
|
ref.read(cardColorsProvider.notifier).save(_tempPrimary, _tempSecondary, _tempGradientType);
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tempPrimary = _savedPrimary;
|
_tempPrimary = _savedPrimary;
|
||||||
_tempSecondary = _savedSecondary;
|
_tempSecondary = _savedSecondary;
|
||||||
|
_tempGradientType = _savedGradientType;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_overlayEntry?.remove();
|
_overlayEntry?.remove();
|
||||||
@@ -210,6 +215,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|||||||
onLongPress: _onCardLongPress,
|
onLongPress: _onCardLongPress,
|
||||||
previewPrimary: _editingCard ? _tempPrimary : null,
|
previewPrimary: _editingCard ? _tempPrimary : null,
|
||||||
previewSecondary: _editingCard ? _tempSecondary : null,
|
previewSecondary: _editingCard ? _tempSecondary : null,
|
||||||
|
previewGradientType: _editingCard ? _tempGradientType : null,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_SummaryRow(
|
_SummaryRow(
|
||||||
@@ -630,6 +636,7 @@ class _BalanceCard extends ConsumerStatefulWidget {
|
|||||||
final VoidCallback? onLongPress;
|
final VoidCallback? onLongPress;
|
||||||
final Color? previewPrimary;
|
final Color? previewPrimary;
|
||||||
final Color? previewSecondary;
|
final Color? previewSecondary;
|
||||||
|
final GradientType? previewGradientType;
|
||||||
|
|
||||||
const _BalanceCard({
|
const _BalanceCard({
|
||||||
required this.balance,
|
required this.balance,
|
||||||
@@ -637,6 +644,7 @@ class _BalanceCard extends ConsumerStatefulWidget {
|
|||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
this.previewPrimary,
|
this.previewPrimary,
|
||||||
this.previewSecondary,
|
this.previewSecondary,
|
||||||
|
this.previewGradientType,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -674,6 +682,41 @@ class _BalanceCardState extends ConsumerState<_BalanceCard>
|
|||||||
super.dispose();
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final rates = ref.read(exchangeRateServiceProvider);
|
final rates = ref.read(exchangeRateServiceProvider);
|
||||||
@@ -681,6 +724,7 @@ class _BalanceCardState extends ConsumerState<_BalanceCard>
|
|||||||
final savedColors = ref.watch(cardColorsProvider);
|
final savedColors = ref.watch(cardColorsProvider);
|
||||||
final primary = widget.previewPrimary ?? savedColors.primary;
|
final primary = widget.previewPrimary ?? savedColors.primary;
|
||||||
final secondary = widget.previewSecondary ?? savedColors.secondary;
|
final secondary = widget.previewSecondary ?? savedColors.secondary;
|
||||||
|
final gradientType = widget.previewGradientType ?? savedColors.gradientType;
|
||||||
final allCurrencies = [
|
final allCurrencies = [
|
||||||
('USD', r'$'),
|
('USD', r'$'),
|
||||||
('EUR', '€'),
|
('EUR', '€'),
|
||||||
@@ -710,16 +754,7 @@ class _BalanceCardState extends ConsumerState<_BalanceCard>
|
|||||||
height: 180,
|
height: 180,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
gradient: LinearGradient(
|
gradient: _buildGradient(primary, secondary, gradientType),
|
||||||
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],
|
|
||||||
),
|
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.4),
|
color: Colors.black.withOpacity(0.4),
|
||||||
@@ -1102,7 +1137,7 @@ class _FullScreenBlurOverlayState extends State<_FullScreenBlurOverlay> {
|
|||||||
final mq = MediaQuery.of(widget.context);
|
final mq = MediaQuery.of(widget.context);
|
||||||
final cardTop = mq.padding.top + kToolbarHeight + 16;
|
final cardTop = mq.padding.top + kToolbarHeight + 16;
|
||||||
final cardHeight = 180.0;
|
final cardHeight = 180.0;
|
||||||
final panelTop = cardTop + cardHeight + 78;
|
final panelTop = cardTop + cardHeight + 50;
|
||||||
final panelBottom = mq.padding.bottom + 16;
|
final panelBottom = mq.padding.bottom + 16;
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
@@ -1140,6 +1175,7 @@ class _FullScreenBlurOverlayState extends State<_FullScreenBlurOverlay> {
|
|||||||
onLongPress: null,
|
onLongPress: null,
|
||||||
previewPrimary: dash._tempPrimary,
|
previewPrimary: dash._tempPrimary,
|
||||||
previewSecondary: dash._tempSecondary,
|
previewSecondary: dash._tempSecondary,
|
||||||
|
previewGradientType: dash._tempGradientType,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1364,9 +1400,127 @@ class _FullScreenBlurOverlayState extends State<_FullScreenBlurOverlay> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// APPLY BUTTON
|
// GRADIENT TYPE SECTION
|
||||||
SizedBox(
|
Text(
|
||||||
width: double.infinity,
|
'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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
// APPLY button
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () => dash._closeOverlay(apply: true),
|
onPressed: () => dash._closeOverlay(apply: true),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@@ -1379,12 +1533,13 @@ class _FullScreenBlurOverlayState extends State<_FullScreenBlurOverlay> {
|
|||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Apply',
|
'Apply',
|
||||||
style:
|
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 15),
|
||||||
TextStyle(fontWeight: FontWeight.w700, fontSize: 15),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user