mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 10:25:28 +03:00
update
This commit is contained in:
@@ -658,7 +658,7 @@ class _TypeOption extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _CategoryPicker extends StatelessWidget {
|
||||
class _CategoryPicker extends ConsumerWidget {
|
||||
final List<String> categories;
|
||||
final String selected;
|
||||
final ValueChanged<String> onChanged;
|
||||
@@ -669,7 +669,8 @@ class _CategoryPicker extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
@@ -696,7 +697,7 @@ class _CategoryPicker extends StatelessWidget {
|
||||
Icon(icon, color: isSelected ? color : Theme.of(context).colorScheme.onSurface.withOpacity(0.6), size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
cat,
|
||||
s.categoryLabel(cat),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: isSelected ? color : Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../core/constants.dart';
|
||||
import '../../core/l10n/locale_provider.dart';
|
||||
@@ -199,6 +198,7 @@ class _PieChartCard extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final fmt = ref.watch(amountFormatProvider);
|
||||
final entries = data.entries.toList();
|
||||
|
||||
@@ -255,7 +255,7 @@ class _PieChartCard extends ConsumerWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Total',
|
||||
s.total,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
@@ -285,6 +285,7 @@ class _BarChartCard extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final fmt = ref.watch(amountFormatProvider);
|
||||
final maxY = monthlyData.map((e) => e.amount).reduce((a, b) => a > b ? a : b);
|
||||
final adjustedMaxY = maxY * 1.2;
|
||||
@@ -299,7 +300,7 @@ class _BarChartCard extends ConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Last 6 Months',
|
||||
s.lastSixMonths,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -497,11 +498,12 @@ class _CategoryRow extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _EmptyState extends StatelessWidget {
|
||||
class _EmptyState extends ConsumerWidget {
|
||||
const _EmptyState();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -520,7 +522,7 @@ class _EmptyState extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'No expense data',
|
||||
s.noExpenseData,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -528,7 +530,7 @@ class _EmptyState extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Add some expenses to see the breakdown',
|
||||
s.addExpensesToSeeBreakdown,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
|
||||
@@ -133,7 +133,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
scrolledUnderElevation: 0,
|
||||
titleSpacing: 20,
|
||||
title: Text(
|
||||
s.appTitle,
|
||||
'Casha',
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
@@ -144,12 +144,18 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: Center(
|
||||
child: Text(
|
||||
DateFormat('MMMM yyyy', s.dateLocale).format(DateTime.now()),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final raw = DateFormat('LLLL, yyyy', s.dateLocale).format(DateTime.now());
|
||||
final capitalized = raw.isNotEmpty ? '${raw[0].toUpperCase()}${raw.substring(1)}' : raw;
|
||||
return Text(
|
||||
capitalized,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,10 +3,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sensors_plus/sensors_plus.dart';
|
||||
import '../../../core/constants.dart';
|
||||
import '../../../core/l10n/locale_provider.dart';
|
||||
import '../../../core/services/card_color_service.dart';
|
||||
import '../../../core/services/haptic_service.dart';
|
||||
import '../../../shared/providers/amount_format_provider.dart';
|
||||
import '../../../shared/utils/currency_utils.dart';
|
||||
import '../../settings/provider.dart';
|
||||
import '../provider.dart';
|
||||
|
||||
@@ -129,6 +129,7 @@ class BalanceCardState extends ConsumerState<BalanceCard>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final rates = ref.read(exchangeRateServiceProvider);
|
||||
final fmt = ref.watch(amountFormatProvider);
|
||||
final savedColors = ref.watch(cardColorsProvider);
|
||||
@@ -196,7 +197,7 @@ class BalanceCardState extends ConsumerState<BalanceCard>
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'TOTAL BALANCE',
|
||||
s.totalBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
letterSpacing: 1.5,
|
||||
@@ -271,7 +272,7 @@ class BalanceCardState extends ConsumerState<BalanceCard>
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Text(
|
||||
'tap and hold to edit',
|
||||
s.tapAndHoldToEdit,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
|
||||
@@ -4,7 +4,6 @@ import '../../../core/l10n/app_strings.dart';
|
||||
import '../../../shared/providers/amount_format_provider.dart';
|
||||
import '../../../shared/utils/currency_utils.dart';
|
||||
import '../../settings/provider.dart';
|
||||
import '../provider.dart';
|
||||
|
||||
class BudgetProgress extends ConsumerWidget {
|
||||
final double spent;
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/l10n/app_strings.dart';
|
||||
import '../../../core/l10n/locale_provider.dart';
|
||||
import '../../../core/services/card_color_service.dart';
|
||||
import '../../../core/services/haptic_service.dart';
|
||||
import '../../settings/provider.dart';
|
||||
import '../provider.dart';
|
||||
import 'balance_card.dart';
|
||||
|
||||
@@ -5,7 +5,6 @@ import '../../../core/l10n/app_strings.dart';
|
||||
import '../../../shared/providers/amount_format_provider.dart';
|
||||
import '../../../shared/utils/currency_utils.dart';
|
||||
import '../../settings/provider.dart';
|
||||
import '../provider.dart';
|
||||
|
||||
class SummaryRow extends StatelessWidget {
|
||||
final double income;
|
||||
|
||||
@@ -5,7 +5,6 @@ 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';
|
||||
@@ -48,52 +47,57 @@ const Map<String, CurrencyInfo> currencyMap = {
|
||||
};
|
||||
|
||||
class CurrencyNotifier extends StateNotifier<CurrencyInfo> {
|
||||
CurrencyNotifier() : super(currencyMap['USD']!) {
|
||||
final SharedPreferences _prefs;
|
||||
|
||||
CurrencyNotifier(this._prefs) : super(currencyMap['USD']!) {
|
||||
_load();
|
||||
}
|
||||
|
||||
void _load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final code = prefs.getString('currency_code') ?? 'USD';
|
||||
void _load() {
|
||||
final code = _prefs.getString('currency_code') ?? 'USD';
|
||||
state = currencyMap[code] ?? currencyMap['USD']!;
|
||||
}
|
||||
|
||||
Future<void> setCurrency(String code) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
state = currencyMap[code] ?? currencyMap['USD']!;
|
||||
await prefs.setString('currency_code', code);
|
||||
await _prefs.setString('currency_code', code);
|
||||
}
|
||||
}
|
||||
|
||||
final currencyProvider = StateNotifierProvider<CurrencyNotifier, CurrencyInfo>(
|
||||
(ref) => CurrencyNotifier(),
|
||||
(ref) {
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
return CurrencyNotifier(prefs);
|
||||
},
|
||||
);
|
||||
|
||||
class ThemeModeNotifier extends StateNotifier<ThemeMode> {
|
||||
ThemeModeNotifier() : super(ThemeMode.dark) {
|
||||
final SharedPreferences _prefs;
|
||||
|
||||
ThemeModeNotifier(this._prefs) : super(ThemeMode.dark) {
|
||||
_load();
|
||||
}
|
||||
|
||||
void _load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
state = (prefs.getBool('dark_mode') ?? true) ? ThemeMode.dark : ThemeMode.light;
|
||||
void _load() {
|
||||
state = (_prefs.getBool('dark_mode') ?? true) ? ThemeMode.dark : ThemeMode.light;
|
||||
}
|
||||
|
||||
Future<void> toggle() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
state = state == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
|
||||
await prefs.setBool('dark_mode', state == ThemeMode.dark);
|
||||
await _prefs.setBool('dark_mode', state == ThemeMode.dark);
|
||||
}
|
||||
|
||||
Future<void> setThemeMode(bool isDark) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
state = isDark ? ThemeMode.dark : ThemeMode.light;
|
||||
await prefs.setBool('dark_mode', isDark);
|
||||
await _prefs.setBool('dark_mode', isDark);
|
||||
}
|
||||
}
|
||||
|
||||
final themeProvider = StateNotifierProvider<ThemeModeNotifier, ThemeMode>(
|
||||
(ref) => ThemeModeNotifier(),
|
||||
(ref) {
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
return ThemeModeNotifier(prefs);
|
||||
},
|
||||
);
|
||||
|
||||
final exchangeRateServiceProvider = Provider<ExchangeRateService>((ref) {
|
||||
|
||||
+258
-699
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/constants.dart';
|
||||
import '../../../core/l10n/locale_provider.dart';
|
||||
import '../../../shared/providers/amount_format_provider.dart';
|
||||
import '../provider.dart';
|
||||
|
||||
class AmountFormatSection extends ConsumerWidget {
|
||||
const AmountFormatSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final fmt = ref.watch(amountFormatProvider);
|
||||
final currencyInfo = ref.watch(currencyProvider);
|
||||
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: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.accent.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.format_list_numbered_rounded,
|
||||
color: AppColors.accent,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
s.amountFormat,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...AmountFormat.values.map((format) {
|
||||
final isSelected = fmt == format;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: GestureDetector(
|
||||
onTap: () => ref.read(amountFormatProvider.notifier).set(format),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? AppColors.accent.withOpacity(0.2)
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: isSelected
|
||||
? Border.all(color: AppColors.accent, width: 1.5)
|
||||
: (isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
format.label,
|
||||
style: TextStyle(
|
||||
color: isSelected ? AppColors.accent : Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
format.example.replaceFirst('SYM', currencyInfo.symbol),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/constants.dart';
|
||||
import '../../../core/l10n/locale_provider.dart';
|
||||
import '../../../shared/providers/amount_format_provider.dart';
|
||||
import '../../../shared/utils/currency_utils.dart';
|
||||
import '../provider.dart';
|
||||
|
||||
class BudgetSection extends ConsumerStatefulWidget {
|
||||
const BudgetSection({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<BudgetSection> createState() => _BudgetSectionState();
|
||||
}
|
||||
|
||||
class _BudgetSectionState extends ConsumerState<BudgetSection> {
|
||||
final _budgetController = TextEditingController();
|
||||
bool _isEditing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final budget = ref.read(budgetProvider);
|
||||
if (budget != null) {
|
||||
_budgetController.text = budget.toStringAsFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_budgetController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _saveBudget() async {
|
||||
final text = _budgetController.text.trim();
|
||||
if (text.isEmpty) {
|
||||
await ref.read(budgetProvider.notifier).setBudget(null);
|
||||
} else {
|
||||
final value = double.tryParse(text);
|
||||
if (value != null && value > 0) {
|
||||
await ref.read(budgetProvider.notifier).setBudget(value);
|
||||
}
|
||||
}
|
||||
setState(() => _isEditing = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final budget = ref.watch(budgetProvider);
|
||||
final currencyInfo = ref.watch(currencyProvider);
|
||||
final fmt = ref.watch(amountFormatProvider);
|
||||
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: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.accent.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.account_balance_wallet_rounded,
|
||||
color: AppColors.accent,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
s.monthlyBudgetSetting,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!_isEditing)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_rounded, size: 20),
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
onPressed: () => setState(() => _isEditing = true),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (_isEditing)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _budgetController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}')),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
prefixText: currencyInfo.symbol == 'Br' || currencyInfo.symbol == '₽'
|
||||
? '${currencyInfo.symbol} '
|
||||
: currencyInfo.symbol,
|
||||
hintText: '0.00',
|
||||
helperText: s.leaveEmptyToRemove,
|
||||
),
|
||||
autofocus: true,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final budget = ref.read(budgetProvider);
|
||||
_budgetController.text = budget?.toStringAsFixed(2) ?? '';
|
||||
setState(() => _isEditing = false);
|
||||
},
|
||||
child: Text(s.cancel),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: _saveBudget,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(80, 40),
|
||||
),
|
||||
child: Text(s.save),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
budget != null
|
||||
? formatAmount(currencyInfo.symbol, budget, fmt)
|
||||
: s.budgetNone,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
color: budget != null ? AppColors.accent : Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
budget != null
|
||||
? s.yourMonthlySpendingLimit
|
||||
: s.setMonthlySpendingLimit,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/constants.dart';
|
||||
import '../../../core/l10n/locale_provider.dart';
|
||||
import '../provider.dart';
|
||||
|
||||
class CurrencySection extends ConsumerWidget {
|
||||
const CurrencySection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final currencyInfo = ref.watch(currencyProvider);
|
||||
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: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.accent.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.attach_money_rounded,
|
||||
color: AppColors.accent,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
s.currency,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: ['USD', 'EUR', 'BYN', 'RUB'].map((code) {
|
||||
final info = currencyMap[code]!;
|
||||
final isSelected = currencyInfo.code == code;
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final oldCode = ref.read(currencyProvider).code;
|
||||
final rates = ref.read(exchangeRateServiceProvider);
|
||||
ref.read(budgetProvider.notifier).onCurrencyChanged(oldCode, code, rates);
|
||||
ref.read(currencyProvider.notifier).setCurrency(code);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? AppColors.accent.withOpacity(0.2)
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: isSelected
|
||||
? Border.all(color: AppColors.accent, width: 1.5)
|
||||
: (isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
info.symbol,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: isSelected ? AppColors.accent : Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
code,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: isSelected ? AppColors.accent : Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/constants.dart';
|
||||
import '../../../core/l10n/locale_provider.dart';
|
||||
import '../provider.dart';
|
||||
|
||||
class HapticSection extends ConsumerWidget {
|
||||
const HapticSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
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(
|
||||
s.hapticFeedback,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
s.vibrationOnInteractions,
|
||||
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),
|
||||
activeThumbColor: const Color(0xFF7C6DED),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/constants.dart';
|
||||
import '../../../core/l10n/app_strings.dart';
|
||||
import '../../../core/l10n/locale_provider.dart';
|
||||
|
||||
class LanguageSection extends ConsumerWidget {
|
||||
const LanguageSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final currentLocale = ref.watch(localeProvider);
|
||||
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: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.accent.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.language_rounded,
|
||||
color: AppColors.accent,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
s.language,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => ref.read(localeProvider.notifier).setLocale(AppLocale.en),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: currentLocale == AppLocale.en
|
||||
? AppColors.accent.withOpacity(0.2)
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: currentLocale == AppLocale.en
|
||||
? Border.all(color: AppColors.accent, width: 1.5)
|
||||
: (isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1)),
|
||||
),
|
||||
child: Text(
|
||||
s.langEn,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: currentLocale == AppLocale.en
|
||||
? AppColors.accent
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
fontWeight: currentLocale == AppLocale.en ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => ref.read(localeProvider.notifier).setLocale(AppLocale.ru),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: currentLocale == AppLocale.ru
|
||||
? AppColors.accent.withOpacity(0.2)
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: currentLocale == AppLocale.ru
|
||||
? Border.all(color: AppColors.accent, width: 1.5)
|
||||
: (isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1)),
|
||||
),
|
||||
child: Text(
|
||||
s.langRu,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: currentLocale == AppLocale.ru
|
||||
? AppColors.accent
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
fontWeight: currentLocale == AppLocale.ru ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/constants.dart';
|
||||
import '../../../core/l10n/locale_provider.dart';
|
||||
import '../provider.dart';
|
||||
|
||||
class ThemeSection extends ConsumerWidget {
|
||||
const ThemeSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final themeMode = ref.watch(themeProvider);
|
||||
final isDarkMode = themeMode == ThemeMode.dark;
|
||||
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: Icon(
|
||||
isDarkMode ? Icons.dark_mode_rounded : Icons.light_mode_rounded,
|
||||
color: AppColors.accent,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
s.darkMode,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
isDarkMode ? s.enabled : s.disabled,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: isDarkMode,
|
||||
onChanged: (value) {
|
||||
ref.read(themeProvider.notifier).setThemeMode(value);
|
||||
},
|
||||
activeThumbColor: AppColors.accent,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user