mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 02:15:29 +03:00
stableee
This commit is contained in:
Binary file not shown.
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user