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