This commit is contained in:
2026-03-26 00:42:54 +03:00
parent d1ef8a64a1
commit 71de991587
18 changed files with 564 additions and 187 deletions
+1
View File
@@ -388,6 +388,7 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
AmountInput(
controller: _amountController,
currencySymbol: overrideCurrency,
currencyCode: state.overrideCurrencyCode,
showError: _showError,
borderColorAnimation: _borderColorAnimation,
isDark: isDark,
@@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import '../../../shared/widgets/byn_sign.dart';
class AmountInput extends StatelessWidget {
final TextEditingController controller;
final String currencySymbol;
final String currencyCode;
final bool showError;
final Animation<Color?> borderColorAnimation;
final bool isDark;
@@ -12,6 +14,7 @@ class AmountInput extends StatelessWidget {
super.key,
required this.controller,
required this.currencySymbol,
required this.currencyCode,
required this.showError,
required this.borderColorAnimation,
required this.isDark,
@@ -41,15 +44,22 @@ class AmountInput extends StatelessWidget {
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: Text(
currencySymbol,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.7),
fontWeight: FontWeight.w600,
),
),
child: currencyCode == 'BYN'
? BynSign(
fontSize: 18,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.7),
)
: Text(
currencySymbol,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.7),
fontWeight: FontWeight.w600,
),
),
),
Expanded(
child: TextField(
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../shared/widgets/byn_sign.dart';
class CurrencyPicker extends StatelessWidget {
final String selected;
@@ -45,16 +46,23 @@ class CurrencyPicker extends StatelessWidget {
),
child: Column(
children: [
Text(
c.$2,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isSelected
? const Color(0xFF7C6DED)
: colorScheme.onSurface,
),
),
c.$1 == 'BYN'
? BynSign(
fontSize: 16,
color: isSelected
? const Color(0xFF7C6DED)
: colorScheme.onSurface,
)
: Text(
c.$2,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isSelected
? const Color(0xFF7C6DED)
: colorScheme.onSurface,
),
),
Text(
c.$1,
style: TextStyle(
+73 -40
View File
@@ -6,6 +6,7 @@ import '../../core/constants.dart';
import '../../core/l10n/locale_provider.dart';
import '../../shared/utils/currency_utils.dart';
import '../../shared/providers/amount_format_provider.dart';
import '../../shared/widgets/byn_sign.dart';
import '../settings/provider.dart';
import 'provider.dart';
@@ -42,9 +43,9 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
title: Text(
s.categories,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
),
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
),
),
actions: [
_ChartToggle(
@@ -78,7 +79,9 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
border: Border.all(
color: !_showIncome
? AppColors.accent
: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.2),
width: 1.5,
),
),
@@ -90,7 +93,9 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
fontWeight: FontWeight.w600,
color: !_showIncome
? Colors.white
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
),
@@ -113,7 +118,9 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
border: Border.all(
color: _showIncome
? AppColors.accent
: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.2),
width: 1.5,
),
),
@@ -125,7 +132,9 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
fontWeight: FontWeight.w600,
color: _showIncome
? Colors.white
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
),
@@ -149,11 +158,15 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
currency: currencyInfo.symbol,
)
else
_BarChartCard(monthlyData: monthlyData, currency: currencyInfo.symbol),
_BarChartCard(
monthlyData: monthlyData,
currency: currencyInfo.symbol,
),
const SizedBox(height: 20),
Text(
s.rankedByAmount,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
style: Theme.of(context).textTheme.titleMedium
?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
@@ -235,12 +248,16 @@ class _ToggleButton extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isSelected ? AppColors.accent.withOpacity(0.15) : Colors.transparent,
color: isSelected
? AppColors.accent.withOpacity(0.15)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: isSelected ? AppColors.accent : Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
color: isSelected
? AppColors.accent
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
size: 20,
),
),
@@ -301,7 +318,8 @@ class _PieChartCard extends ConsumerWidget {
final isTouched = i == touchedIndex;
final cat = entries[i].key;
final val = entries[i].value;
final color = AppCategories.colors[cat] ?? AppColors.accent;
final color =
AppCategories.colors[cat] ?? AppColors.accent;
return PieChartSectionData(
color: color,
value: val,
@@ -324,15 +342,17 @@ class _PieChartCard extends ConsumerWidget {
Text(
s.total,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
Text(
formatAmount(currency, total, fmt),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w700,
),
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w700,
),
),
],
),
@@ -354,7 +374,9 @@ class _BarChartCard extends ConsumerWidget {
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 maxY = monthlyData
.map((e) => e.amount)
.reduce((a, b) => a > b ? a : b);
final adjustedMaxY = maxY * 1.2;
return Container(
@@ -369,9 +391,9 @@ class _BarChartCard extends ConsumerWidget {
Text(
s.lastSixMonths,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
fontWeight: FontWeight.w600,
),
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
SizedBox(
@@ -400,14 +422,17 @@ class _BarChartCard extends ConsumerWidget {
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
if (value.toInt() >= 0 && value.toInt() < monthlyData.length) {
if (value.toInt() >= 0 &&
value.toInt() < monthlyData.length) {
final month = monthlyData[value.toInt()].month;
return Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
DateFormat('MMM').format(month),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
fontSize: 11,
),
),
@@ -501,15 +526,21 @@ class _CategoryRow extends ConsumerWidget {
height: 28,
alignment: Alignment.center,
decoration: BoxDecoration(
color: rank <= 3 ? color.withOpacity(0.2) : Theme.of(context).dividerColor,
color: rank <= 3
? color.withOpacity(0.2)
: Theme.of(context).dividerColor,
shape: BoxShape.circle,
),
child: Text(
'$rank',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: rank <= 3 ? color : Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
fontWeight: FontWeight.w700,
),
color: rank <= 3
? color
: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(width: 10),
@@ -526,9 +557,9 @@ class _CategoryRow extends ConsumerWidget {
child: Text(
s.categoryLabel(category),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
Column(
@@ -537,15 +568,17 @@ class _CategoryRow extends ConsumerWidget {
Text(
formatAmount(currency, amount, fmt),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: isIncome ? AppColors.income : AppColors.expense,
fontWeight: FontWeight.w700,
),
color: isIncome ? AppColors.income : AppColors.expense,
fontWeight: FontWeight.w700,
),
),
Text(
'${(pct * 100).toStringAsFixed(1)}%',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
@@ -594,16 +627,16 @@ class _EmptyState extends ConsumerWidget {
Text(
isIncome ? s.noIncomeData : s.noExpenseData,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
Text(
isIncome ? s.addIncomeToSeeBreakdown : s.addExpensesToSeeBreakdown,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../shared/models/account.dart';
import '../../../../shared/models/transaction.dart';
import '../../../../shared/widgets/byn_sign.dart';
import '../../../settings/provider.dart';
import '../../provider.dart';
import '../balance_card.dart';
@@ -308,16 +309,25 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
entry.$2,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: isSelected
? const Color(0xFF7C6DED)
: null,
),
),
entry.$1 == 'BYN'
? BynSign(
fontSize: 14,
color: isSelected
? const Color(0xFF7C6DED)
: Theme.of(
widget.context,
).colorScheme.onSurface,
)
: Text(
entry.$2,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: isSelected
? const Color(0xFF7C6DED)
: null,
),
),
const SizedBox(width: 4),
Flexible(
child: Text(
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../shared/widgets/byn_sign.dart';
class AccountEditorPanel extends ConsumerWidget {
final TextEditingController nameController;
@@ -160,18 +161,29 @@ class AccountEditorPanel extends ConsumerWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
[
('USD', '\$'),
('EUR', ''),
('BYN', 'Br'),
('RUB', ''),
].firstWhere((c) => c.$1 == selectedCurrency).$2,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
selectedCurrency == 'BYN'
? BynSign(
fontSize: 15,
color: Theme.of(
dashboardContext,
).colorScheme.onSurface,
)
: Text(
[
('USD', '\$'),
('EUR', ''),
('BYN', 'Br'),
('RUB', ''),
]
.firstWhere(
(c) => c.$1 == selectedCurrency,
)
.$2,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 4),
Icon(
showCurrencyDropdown
@@ -7,12 +7,13 @@ 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/widgets/byn_sign.dart';
import '../../settings/provider.dart';
import '../provider.dart';
String _smartBalance(double amount, AmountFormat fmt, String symbol) {
const spaceAfter = {'Br'};
final sep = spaceAfter.contains(symbol) ? ' ' : '';
final sep = spaceAfter.contains(symbol) || symbol.isEmpty ? ' ' : '';
final isWhole = amount == amount.floorToDouble();
String formatted;
@@ -24,7 +25,7 @@ String _smartBalance(double amount, AmountFormat fmt, String symbol) {
} else {
formatted = fmt.format(amount);
}
return '$symbol$sep$formatted';
return symbol.isEmpty ? formatted : '$symbol$sep$formatted';
}
class BalanceCard extends ConsumerStatefulWidget {
@@ -242,19 +243,45 @@ class BalanceCardState extends ConsumerState<BalanceCard>
FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.center,
child: Text(
_smartBalance(
widget.balance,
fmt,
widget.currencyInfo.symbol,
),
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.w700,
color: onCard,
),
maxLines: 1,
),
child: widget.currencyInfo.code == 'BYN'
? Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
BynSign(
fontSize: 48,
color: onCard,
),
const SizedBox(width: 4),
Text(
_smartBalance(
widget.balance,
fmt,
'',
),
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.w700,
color: onCard,
),
maxLines: 1,
),
],
)
: Text(
_smartBalance(
widget.balance,
fmt,
widget.currencyInfo.symbol,
),
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.w700,
color: onCard,
),
maxLines: 1,
),
),
],
),
@@ -285,15 +312,49 @@ class BalanceCardState extends ConsumerState<BalanceCard>
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
_smartBalance(converted, fmt, c.$2),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: onCard.withOpacity(0.65),
),
maxLines: 1,
),
child: c.$1 == 'BYN'
? Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
BynSign(
fontSize: 14,
color: onCard.withOpacity(
0.65,
),
),
const SizedBox(width: 2),
Text(
_smartBalance(
converted,
fmt,
'',
),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: onCard.withOpacity(
0.65,
),
),
maxLines: 1,
),
],
)
: Text(
_smartBalance(
converted,
fmt,
c.$2,
),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: onCard.withOpacity(0.65),
),
maxLines: 1,
),
),
);
}).toList(),
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/l10n/app_strings.dart';
import '../../../shared/providers/amount_format_provider.dart';
import '../../../shared/utils/currency_utils.dart';
import '../../../shared/widgets/byn_sign.dart';
import '../../settings/provider.dart';
class BudgetProgress extends ConsumerWidget {
@@ -96,22 +97,86 @@ class BudgetProgress extends ConsumerWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${strings.spent}: ${formatAmount(currencyInfo.symbol, spent, fmt)}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
Text(
'${strings.limit}: ${formatAmount(currencyInfo.symbol, budget, fmt)}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
currencyInfo.code == 'BYN'
? Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'${strings.spent}: ',
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
BynSign(
fontSize: 12,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
const SizedBox(width: 2),
Text(
formatAmount('', spent, fmt),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
],
)
: Text(
'${strings.spent}: ${formatAmount(currencyInfo.symbol, spent, fmt)}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
currencyInfo.code == 'BYN'
? Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'${strings.limit}: ',
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
BynSign(
fontSize: 12,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
const SizedBox(width: 2),
Text(
formatAmount('', budget, fmt),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
],
)
: Text(
'${strings.limit}: ${formatAmount(currencyInfo.symbol, budget, fmt)}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
],
@@ -4,6 +4,7 @@ import '../../../core/constants.dart';
import '../../../core/l10n/app_strings.dart';
import '../../../shared/providers/amount_format_provider.dart';
import '../../../shared/utils/currency_utils.dart';
import '../../../shared/widgets/byn_sign.dart';
import '../../settings/provider.dart';
class SummaryRow extends StatelessWidget {
@@ -104,14 +105,34 @@ class SummaryCard extends ConsumerWidget {
),
),
const SizedBox(height: 2),
Text(
formatAmount(currencyInfo.symbol, amount, fmt),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
currencyInfo.code == 'BYN'
? Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
BynSign(fontSize: 14, color: color),
const SizedBox(width: 2),
Flexible(
child: Text(
formatAmount('', amount, fmt),
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: color,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
),
],
)
: Text(
formatAmount(currencyInfo.symbol, amount, fmt),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
@@ -9,6 +9,7 @@ import '../../../core/l10n/locale_provider.dart';
import '../../../shared/models/transaction.dart';
import '../../../shared/providers/amount_format_provider.dart';
import '../../../shared/utils/currency_utils.dart';
import '../../../shared/widgets/byn_sign.dart';
import '../provider.dart';
class TransactionTile extends ConsumerWidget {
@@ -117,13 +118,36 @@ class TransactionTile extends ConsumerWidget {
],
),
),
Text(
'${isIncome ? '+ ' : '- '}${formatAmount(transaction.currency, transaction.amount, fmt)}',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
transaction.currencyCode == 'BYN'
? Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
isIncome ? '+ ' : '- ',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
BynSign(fontSize: 14, color: color),
const SizedBox(width: 2),
Text(
formatAmount('', transaction.amount, fmt),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
],
)
: Text(
'${isIncome ? '+ ' : '- '}${formatAmount(transaction.currency, transaction.amount, fmt)}',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
],
),
),
+1 -1
View File
@@ -46,7 +46,7 @@ class CurrencyInfo {
const Map<String, CurrencyInfo> currencyMap = {
'USD': CurrencyInfo('\$', 'USD'),
'EUR': CurrencyInfo('', 'EUR'),
'BYN': CurrencyInfo('Br', 'BYN'),
'BYN': CurrencyInfo('', 'BYN'),
'RUB': CurrencyInfo('', 'RUB'),
};
@@ -20,7 +20,9 @@ class AmountFormatSection extends ConsumerWidget {
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
border: isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1),
border: isDark
? null
: Border.all(color: const Color(0xFFDDDDEE), width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -44,9 +46,9 @@ class AmountFormatSection extends ConsumerWidget {
child: Text(
s.amountFormat,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
],
@@ -57,9 +59,13 @@ class AmountFormatSection extends ConsumerWidget {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: GestureDetector(
onTap: () => ref.read(amountFormatProvider.notifier).set(format),
onTap: () =>
ref.read(amountFormatProvider.notifier).set(format),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
color: isSelected
? AppColors.accent.withOpacity(0.2)
@@ -67,7 +73,12 @@ class AmountFormatSection extends ConsumerWidget {
borderRadius: BorderRadius.circular(12),
border: isSelected
? Border.all(color: AppColors.accent, width: 1.5)
: (isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1)),
: (isDark
? null
: Border.all(
color: const Color(0xFFDDDDEE),
width: 1,
)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -75,14 +86,25 @@ class AmountFormatSection extends ConsumerWidget {
Text(
format.label,
style: TextStyle(
color: isSelected ? AppColors.accent : Theme.of(context).colorScheme.onSurface,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
color: isSelected
? AppColors.accent
: Theme.of(context).colorScheme.onSurface,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.w500,
),
),
Text(
format.example.replaceFirst('SYM', currencyInfo.symbol),
format.example.replaceFirst(
'SYM',
currencyInfo.symbol.isEmpty
? 'Br'
: currencyInfo.symbol,
),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
fontSize: 12,
),
),
@@ -5,6 +5,7 @@ import '../../../core/constants.dart';
import '../../../core/l10n/locale_provider.dart';
import '../../../shared/providers/amount_format_provider.dart';
import '../../../shared/utils/currency_utils.dart';
import '../../../shared/widgets/byn_sign.dart';
import '../provider.dart';
class BudgetSection extends ConsumerStatefulWidget {
@@ -59,7 +60,9 @@ class _BudgetSectionState extends ConsumerState<BudgetSection> {
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
border: isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1),
border: isDark
? null
: Border.all(color: const Color(0xFFDDDDEE), width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -83,15 +86,17 @@ class _BudgetSectionState extends ConsumerState<BudgetSection> {
child: Text(
s.monthlyBudgetSetting,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
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),
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
onPressed: () => setState(() => _isEditing = true),
),
],
@@ -103,14 +108,32 @@ class _BudgetSectionState extends ConsumerState<BudgetSection> {
children: [
TextFormField(
controller: _budgetController,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}')),
FilteringTextInputFormatter.allow(
RegExp(r'^\d+\.?\d{0,2}'),
),
],
decoration: InputDecoration(
prefixText: currencyInfo.symbol == 'Br' || currencyInfo.symbol == ''
? '${currencyInfo.symbol} '
: currencyInfo.symbol,
prefix: currencyInfo.code == 'BYN'
? Row(
mainAxisSize: MainAxisSize.min,
children: [
BynSign(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
),
const SizedBox(width: 4),
],
)
: null,
prefixText: currencyInfo.code != 'BYN'
? (currencyInfo.symbol == ''
? '${currencyInfo.symbol} '
: currencyInfo.symbol)
: null,
hintText: '0.00',
helperText: s.leaveEmptyToRemove,
),
@@ -123,7 +146,8 @@ class _BudgetSectionState extends ConsumerState<BudgetSection> {
TextButton(
onPressed: () {
final budget = ref.read(budgetProvider);
_budgetController.text = budget?.toStringAsFixed(2) ?? '';
_budgetController.text =
budget?.toStringAsFixed(2) ?? '';
setState(() => _isEditing = false);
},
child: Text(s.cancel),
@@ -144,23 +168,47 @@ class _BudgetSectionState extends ConsumerState<BudgetSection> {
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,
budget != null && currencyInfo.code == 'BYN'
? Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
BynSign(fontSize: 24, color: AppColors.accent),
const SizedBox(width: 2),
Text(
formatAmount('', budget, fmt),
style: Theme.of(context).textTheme.headlineSmall
?.copyWith(
color: AppColors.accent,
fontWeight: FontWeight.w700,
),
),
],
)
: 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),
),
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/constants.dart';
import '../../../core/l10n/locale_provider.dart';
import '../../../shared/widgets/byn_sign.dart';
import '../provider.dart';
class CurrencySection extends ConsumerWidget {
@@ -18,7 +19,9 @@ class CurrencySection extends ConsumerWidget {
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
border: isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1),
border: isDark
? null
: Border.all(color: const Color(0xFFDDDDEE), width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -42,9 +45,9 @@ class CurrencySection extends ConsumerWidget {
child: Text(
s.currency,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
],
@@ -61,7 +64,9 @@ class CurrencySection extends ConsumerWidget {
onTap: () {
final oldCode = ref.read(currencyProvider).code;
final rates = ref.read(exchangeRateServiceProvider);
ref.read(budgetProvider.notifier).onCurrencyChanged(oldCode, code, rates);
ref
.read(budgetProvider.notifier)
.onCurrencyChanged(oldCode, code, rates);
ref.read(currencyProvider.notifier).setCurrency(code);
},
child: Container(
@@ -71,25 +76,52 @@ class CurrencySection extends ConsumerWidget {
? AppColors.accent.withOpacity(0.2)
: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(12),
border: isSelected
border: isSelected
? Border.all(color: AppColors.accent, width: 1.5)
: (isDark ? null : Border.all(color: const Color(0xFFDDDDEE), width: 1)),
: (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,
code == 'BYN'
? BynSign(
fontSize: 28,
color: isSelected
? AppColors.accent
: Theme.of(context).colorScheme.onSurface
.withOpacity(0.6),
)
: 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,
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,
),
),
],
+4 -2
View File
@@ -3,6 +3,8 @@ import '../../core/constants.dart';
String formatAmount(String symbol, double amount, AmountFormat fmt) {
const spaceAfter = {'Br'};
final formatted = fmt.format(amount);
final sep = spaceAfter.contains(symbol) ? ' ' : '';
return '$symbol$sep$formatted';
// For BYN, symbol is empty string, so we use 'Br' for text-only contexts like CSV
final displaySymbol = symbol.isEmpty ? 'Br' : symbol;
final sep = spaceAfter.contains(displaySymbol) ? ' ' : '';
return '$displaySymbol$sep$formatted';
}
+25
View File
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
class BynSign extends StatelessWidget {
final double fontSize;
final Color color;
const BynSign({super.key, required this.fontSize, required this.color});
@override
Widget build(BuildContext context) {
return Transform.translate(
offset: Offset(0, fontSize * -0.12),
child: Text(
'\uE901',
style: TextStyle(
fontFamily: 'BynSymbol',
fontSize: fontSize,
color: color,
fontWeight: FontWeight.w700,
height: 1.0,
),
),
);
}
}