diff --git a/assets/fonts/nbrb.ttf b/assets/fonts/nbrb.ttf new file mode 100644 index 0000000..3bdee33 Binary files /dev/null and b/assets/fonts/nbrb.ttf differ diff --git a/lib/features/add_transaction/screen.dart b/lib/features/add_transaction/screen.dart index f3e8bb9..e29dac1 100644 --- a/lib/features/add_transaction/screen.dart +++ b/lib/features/add_transaction/screen.dart @@ -388,6 +388,7 @@ class _AddTransactionScreenState extends ConsumerState AmountInput( controller: _amountController, currencySymbol: overrideCurrency, + currencyCode: state.overrideCurrencyCode, showError: _showError, borderColorAnimation: _borderColorAnimation, isDark: isDark, diff --git a/lib/features/add_transaction/widgets/amount_input.dart b/lib/features/add_transaction/widgets/amount_input.dart index b45b388..2e18aa8 100644 --- a/lib/features/add_transaction/widgets/amount_input.dart +++ b/lib/features/add_transaction/widgets/amount_input.dart @@ -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 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( diff --git a/lib/features/add_transaction/widgets/currency_picker.dart b/lib/features/add_transaction/widgets/currency_picker.dart index 34392d0..85273f0 100644 --- a/lib/features/add_transaction/widgets/currency_picker.dart +++ b/lib/features/add_transaction/widgets/currency_picker.dart @@ -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( diff --git a/lib/features/categories/screen.dart b/lib/features/categories/screen.dart index 76f90f7..a73cb9c 100644 --- a/lib/features/categories/screen.dart +++ b/lib/features/categories/screen.dart @@ -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 { 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 { 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 { 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 { 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 { 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 { 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), + ), ), ], ), diff --git a/lib/features/dashboard/widgets/account_editor_overlay/account_editor_overlay.dart b/lib/features/dashboard/widgets/account_editor_overlay/account_editor_overlay.dart index 803896b..fc9a812 100644 --- a/lib/features/dashboard/widgets/account_editor_overlay/account_editor_overlay.dart +++ b/lib/features/dashboard/widgets/account_editor_overlay/account_editor_overlay.dart @@ -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 { 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( diff --git a/lib/features/dashboard/widgets/account_editor_overlay/editor_panel.dart b/lib/features/dashboard/widgets/account_editor_overlay/editor_panel.dart index 4da5b9c..464097d 100644 --- a/lib/features/dashboard/widgets/account_editor_overlay/editor_panel.dart +++ b/lib/features/dashboard/widgets/account_editor_overlay/editor_panel.dart @@ -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 diff --git a/lib/features/dashboard/widgets/balance_card.dart b/lib/features/dashboard/widgets/balance_card.dart index a406338..92bad09 100644 --- a/lib/features/dashboard/widgets/balance_card.dart +++ b/lib/features/dashboard/widgets/balance_card.dart @@ -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 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 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(), diff --git a/lib/features/dashboard/widgets/budget_progress.dart b/lib/features/dashboard/widgets/budget_progress.dart index c9e38f2..b49a365 100644 --- a/lib/features/dashboard/widgets/budget_progress.dart +++ b/lib/features/dashboard/widgets/budget_progress.dart @@ -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), + ), + ), ], ), ], diff --git a/lib/features/dashboard/widgets/summary_row.dart b/lib/features/dashboard/widgets/summary_row.dart index dc3cd91..a763f9f 100644 --- a/lib/features/dashboard/widgets/summary_row.dart +++ b/lib/features/dashboard/widgets/summary_row.dart @@ -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, + ), ], ), ), diff --git a/lib/features/dashboard/widgets/transaction_tile.dart b/lib/features/dashboard/widgets/transaction_tile.dart index 7c86a05..7badcbb 100644 --- a/lib/features/dashboard/widgets/transaction_tile.dart +++ b/lib/features/dashboard/widgets/transaction_tile.dart @@ -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, + ), + ), ], ), ), diff --git a/lib/features/settings/provider.dart b/lib/features/settings/provider.dart index 01f6bae..2b9bb8f 100644 --- a/lib/features/settings/provider.dart +++ b/lib/features/settings/provider.dart @@ -46,7 +46,7 @@ class CurrencyInfo { const Map currencyMap = { 'USD': CurrencyInfo('\$', 'USD'), 'EUR': CurrencyInfo('€', 'EUR'), - 'BYN': CurrencyInfo('Br', 'BYN'), + 'BYN': CurrencyInfo('', 'BYN'), 'RUB': CurrencyInfo('₽', 'RUB'), }; diff --git a/lib/features/settings/widgets/amount_format_section.dart b/lib/features/settings/widgets/amount_format_section.dart index c844226..ff01159 100644 --- a/lib/features/settings/widgets/amount_format_section.dart +++ b/lib/features/settings/widgets/amount_format_section.dart @@ -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, ), ), diff --git a/lib/features/settings/widgets/budget_section.dart b/lib/features/settings/widgets/budget_section.dart index 089b509..b5c00b7 100644 --- a/lib/features/settings/widgets/budget_section.dart +++ b/lib/features/settings/widgets/budget_section.dart @@ -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 { 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 { 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 { 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 { 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 { 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), + ), ), ], ), diff --git a/lib/features/settings/widgets/currency_section.dart b/lib/features/settings/widgets/currency_section.dart index ae6ee1b..8495a70 100644 --- a/lib/features/settings/widgets/currency_section.dart +++ b/lib/features/settings/widgets/currency_section.dart @@ -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, ), ), ], diff --git a/lib/shared/utils/currency_utils.dart b/lib/shared/utils/currency_utils.dart index 1c767cb..85aab54 100644 --- a/lib/shared/utils/currency_utils.dart +++ b/lib/shared/utils/currency_utils.dart @@ -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'; } diff --git a/lib/shared/widgets/byn_sign.dart b/lib/shared/widgets/byn_sign.dart new file mode 100644 index 0000000..5259189 --- /dev/null +++ b/lib/shared/widgets/byn_sign.dart @@ -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, + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 9f9a48d..634329d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -59,6 +59,9 @@ dev_dependencies: flutter: uses-material-design: true fonts: + - family: BynSymbol + fonts: + - asset: assets/fonts/nbrb.ttf - family: NunitoCyrillic fonts: - asset: assets/fonts/nunito/Nunito-Medium.ttf