From 65c31b4076e0cfee3119ed3f8eb327563d479d0e Mon Sep 17 00:00:00 2001 From: kolo Date: Sat, 21 Mar 2026 09:51:02 +0300 Subject: [PATCH] update --- lib/core/services/haptic_service.dart | 44 +++++++++++++++++ lib/features/add_transaction/screen.dart | 3 ++ lib/features/dashboard/screen.dart | 17 +++++-- lib/features/settings/provider.dart | 23 +++++++++ lib/features/settings/screen.dart | 60 ++++++++++++++++++++++++ lib/main.dart | 2 + 6 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 lib/core/services/haptic_service.dart diff --git a/lib/core/services/haptic_service.dart b/lib/core/services/haptic_service.dart new file mode 100644 index 0000000..178b6c7 --- /dev/null +++ b/lib/core/services/haptic_service.dart @@ -0,0 +1,44 @@ +import 'package:flutter/services.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class HapticService { + static const _key = 'haptic_enabled'; + static bool _enabled = true; // runtime cache + + static Future init() async { + final prefs = await SharedPreferences.getInstance(); + _enabled = prefs.getBool(_key) ?? true; + } + + static bool get isEnabled => _enabled; + + static Future setEnabled(bool value) async { + _enabled = value; + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_key, value); + } + + // Light tap — filter chips, toggles, small interactions + static void light() { + if (!_enabled) return; + HapticFeedback.lightImpact(); + } + + // Medium — confirm button, apply, save + static void medium() { + if (!_enabled) return; + HapticFeedback.mediumImpact(); + } + + // Heavy — long press on balance card + static void heavy() { + if (!_enabled) return; + HapticFeedback.heavyImpact(); + } + + // Selection click — switching tabs, filter chips + static void selection() { + if (!_enabled) return; + HapticFeedback.selectionClick(); + } +} diff --git a/lib/features/add_transaction/screen.dart b/lib/features/add_transaction/screen.dart index 7abb124..b5f874d 100644 --- a/lib/features/add_transaction/screen.dart +++ b/lib/features/add_transaction/screen.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:uuid/uuid.dart'; import '../../core/constants.dart'; +import '../../core/services/haptic_service.dart'; import '../../shared/models/transaction.dart'; import '../dashboard/provider.dart'; import '../settings/provider.dart'; @@ -158,6 +159,8 @@ class _AddTransactionScreenState extends ConsumerState } ref.read(addTransactionProvider(widget.initial).notifier).setSubmitting(false); + + HapticService.medium(); if (mounted) context.pop(); } diff --git a/lib/features/dashboard/screen.dart b/lib/features/dashboard/screen.dart index 6088c47..370b41b 100644 --- a/lib/features/dashboard/screen.dart +++ b/lib/features/dashboard/screen.dart @@ -9,6 +9,7 @@ import 'package:intl/intl.dart'; import 'package:sensors_plus/sensors_plus.dart'; import '../../core/constants.dart'; import '../../core/services/card_color_service.dart'; +import '../../core/services/haptic_service.dart'; import '../../shared/models/transaction.dart'; import '../../shared/utils/currency_utils.dart'; import '../../shared/providers/amount_format_provider.dart'; @@ -108,6 +109,7 @@ class _DashboardScreenState extends ConsumerState { void _closeOverlay({required bool apply}) { if (apply) { + HapticService.medium(); ref.read(cardColorsProvider.notifier).save(_tempPrimary, _tempSecondary, _tempGradientType); } else { setState(() { @@ -191,7 +193,10 @@ class _DashboardScreenState extends ConsumerState { ], ), floatingActionButton: FloatingActionButton.extended( - onPressed: () => context.push('/add'), + onPressed: () { + HapticService.medium(); + context.push('/add'); + }, backgroundColor: const Color(0xFF7C6DED), foregroundColor: Colors.white, icon: const Icon(Icons.add), @@ -486,7 +491,10 @@ class _FilterChip extends StatelessWidget { final isDark = Theme.of(context).brightness == Brightness.dark; return GestureDetector( - onTap: onTap, + onTap: () { + HapticService.selection(); + onTap(); + }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), @@ -745,7 +753,10 @@ class _BalanceCardState extends ConsumerState<_BalanceCard> .toList(); return GestureDetector( - onLongPress: widget.onLongPress, + onLongPress: () { + HapticService.heavy(); + widget.onLongPress?.call(); + }, child: AnimatedBuilder( animation: _controller, builder: (context, _) { diff --git a/lib/features/settings/provider.dart b/lib/features/settings/provider.dart index f7f82eb..f3fdcec 100644 --- a/lib/features/settings/provider.dart +++ b/lib/features/settings/provider.dart @@ -1,10 +1,12 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../core/constants.dart'; +import '../../core/services/haptic_service.dart'; import '../../shared/services/exchange_rate_service.dart'; import '../../shared/utils/currency_utils.dart'; import '../../shared/providers/amount_format_provider.dart'; @@ -103,6 +105,27 @@ final ratesInitProvider = FutureProvider((ref) async { await ref.read(exchangeRateServiceProvider).fetchRates(); }); +final hapticEnabledProvider = StateNotifierProvider((ref) { + return HapticNotifier(); +}); + +class HapticNotifier extends StateNotifier { + HapticNotifier() : super(true) { + _load(); + } + + Future _load() async { + state = HapticService.isEnabled; + } + + Future toggle(bool value) async { + await HapticService.setEnabled(value); + state = value; + // Give tactile confirmation when turning ON + if (value) HapticFeedback.mediumImpact(); + } +} + final exportProvider = Provider((ref) { return ExportService(ref); }); diff --git a/lib/features/settings/screen.dart b/lib/features/settings/screen.dart index 74f3dba..b68a4a6 100644 --- a/lib/features/settings/screen.dart +++ b/lib/features/settings/screen.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../core/constants.dart'; import '../../core/services/biometric_service.dart'; +import '../../core/services/haptic_service.dart'; import '../../shared/utils/currency_utils.dart'; import '../../shared/providers/amount_format_provider.dart'; import '../dashboard/provider.dart'; @@ -191,6 +192,64 @@ class _SettingsScreenState extends ConsumerState { ), const SizedBox(height: 16), + Consumer( + builder: (context, ref, _) { + final enabled = ref.watch(hapticEnabledProvider); + final isDark = Theme.of(context).brightness == Brightness.dark; + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(16), + border: isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.accent.withOpacity(0.15), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.vibration_rounded, + color: AppColors.accent, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Haptic Feedback', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + Text( + 'Vibration on interactions', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), + ), + ], + ), + ), + Switch( + value: enabled, + onChanged: (val) => ref.read(hapticEnabledProvider.notifier).toggle(val), + activeColor: const Color(0xFF7C6DED), + ), + ], + ), + ); + }, + ), + const SizedBox(height: 16), + const _BiometricSection(), Container( @@ -584,6 +643,7 @@ class _BiometricSectionState extends State<_BiometricSection> { if (val) { final ok = await BiometricService.authenticate(); if (!ok) return; + HapticService.light(); } await BiometricService.setEnabled(val); if (mounted) setState(() => _enabled = val); diff --git a/lib/main.dart b/lib/main.dart index ca4c7a1..506bf48 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,11 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'app/app.dart'; +import 'core/services/haptic_service.dart'; import 'features/dashboard/provider.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); final prefs = await SharedPreferences.getInstance(); + await HapticService.init(); runApp( ProviderScope(