mirror of
https://github.com/koloideal/Casha.git
synced 2026-08-08 17:51:14 +03:00
step
This commit is contained in:
+151
-507
@@ -1,7 +1,6 @@
|
|||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
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 'package:intl/intl.dart';
|
|
||||||
import '../../core/constants.dart';
|
import '../../core/constants.dart';
|
||||||
import '../../core/l10n/app_strings.dart';
|
import '../../core/l10n/app_strings.dart';
|
||||||
import '../../core/l10n/locale_provider.dart';
|
import '../../core/l10n/locale_provider.dart';
|
||||||
@@ -10,13 +9,9 @@ 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 '../../shared/widgets/byn_sign.dart';
|
||||||
import '../dashboard/provider.dart';
|
import '../dashboard/provider.dart';
|
||||||
import '../dashboard/widgets/summary_row.dart';
|
|
||||||
import '../settings/provider.dart';
|
import '../settings/provider.dart';
|
||||||
import 'provider.dart';
|
import 'provider.dart';
|
||||||
import 'widgets/account_scope_chips.dart';
|
import 'widgets/account_scope_chips.dart';
|
||||||
import 'widgets/stats_hero_card.dart';
|
|
||||||
|
|
||||||
enum ChartType { pie, bar }
|
|
||||||
|
|
||||||
class CategoriesScreen extends ConsumerStatefulWidget {
|
class CategoriesScreen extends ConsumerStatefulWidget {
|
||||||
const CategoriesScreen({super.key});
|
const CategoriesScreen({super.key});
|
||||||
@@ -27,30 +22,17 @@ class CategoriesScreen extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
|
class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
|
||||||
int _touchedIndex = -1;
|
int _touchedIndex = -1;
|
||||||
ChartType _chartType = ChartType.pie;
|
|
||||||
bool _showIncome = false;
|
bool _showIncome = false;
|
||||||
|
|
||||||
String _scopeLabel(AppStrings s) {
|
|
||||||
final activeAccount = ref.watch(activeAccountProvider);
|
|
||||||
if (activeAccount == null) return s.allAccounts;
|
|
||||||
return activeAccount.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = ref.watch(stringsProvider);
|
final s = ref.watch(stringsProvider);
|
||||||
final data = _showIncome
|
final data = _showIncome
|
||||||
? ref.watch(categoryIncomeProvider)
|
? ref.watch(categoryIncomeProvider)
|
||||||
: ref.watch(categoryExpenseProvider);
|
: ref.watch(categoryExpenseProvider);
|
||||||
final monthlyData = _showIncome
|
|
||||||
? ref.watch(monthlyIncomeBreakdownProvider)
|
|
||||||
: ref.watch(monthlyBreakdownProvider);
|
|
||||||
final total = data.values.fold(0.0, (a, b) => a + b);
|
final total = data.values.fold(0.0, (a, b) => a + b);
|
||||||
final currencyInfo = ref.watch(statsCurrencyProvider);
|
final currencyInfo = ref.watch(statsCurrencyProvider);
|
||||||
final income = ref.watch(statsIncomeTotalProvider);
|
|
||||||
final expense = ref.watch(statsExpenseTotalProvider);
|
|
||||||
final timeFilter = ref.watch(timeFilterProvider);
|
final timeFilter = ref.watch(timeFilterProvider);
|
||||||
final scopeLabel = _scopeLabel(s);
|
|
||||||
|
|
||||||
final sortedEntries = data.entries.toList()
|
final sortedEntries = data.entries.toList()
|
||||||
..sort((a, b) => b.value.compareTo(a.value));
|
..sort((a, b) => b.value.compareTo(a.value));
|
||||||
@@ -65,13 +47,6 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
|
|||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
_ChartToggle(
|
|
||||||
selected: _chartType,
|
|
||||||
onChanged: (t) => setState(() => _chartType = t),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -80,81 +55,80 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const AccountScopeChips(),
|
const AccountScopeChips(),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 16),
|
||||||
StatsHeroCard(
|
|
||||||
amount: total,
|
|
||||||
label: _showIncome ? s.income : s.expenses,
|
|
||||||
accentColor: _showIncome ? AppColors.income : AppColors.expense,
|
|
||||||
scopeLabel: scopeLabel,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
SummaryRow(
|
|
||||||
income: income,
|
|
||||||
expense: expense,
|
|
||||||
currencyInfo: currencyInfo,
|
|
||||||
strings: s,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
_StatsTimeFilter(
|
|
||||||
strings: s,
|
|
||||||
timeFilter: timeFilter,
|
|
||||||
onAllTime: () =>
|
|
||||||
ref.read(timeFilterProvider.notifier).set(TimeFilter.allTime),
|
|
||||||
onMonth: () => ref
|
|
||||||
.read(timeFilterProvider.notifier)
|
|
||||||
.set(TimeFilter.lastMonth),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _TypeToggle(
|
child: _TimeFilterChip(
|
||||||
label: s.expenses,
|
label: s.filterAllTime,
|
||||||
isSelected: !_showIncome,
|
isSelected: timeFilter == TimeFilter.allTime,
|
||||||
color: AppColors.expense,
|
onTap: () {
|
||||||
onTap: () => setState(() {
|
HapticService.selection();
|
||||||
_showIncome = false;
|
ref.read(timeFilterProvider.notifier).set(TimeFilter.allTime);
|
||||||
_touchedIndex = -1;
|
},
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _TypeToggle(
|
child: _TimeFilterChip(
|
||||||
label: s.income,
|
label: s.filterMonth,
|
||||||
isSelected: _showIncome,
|
isSelected: timeFilter == TimeFilter.lastMonth,
|
||||||
color: AppColors.income,
|
onTap: () {
|
||||||
onTap: () => setState(() {
|
HapticService.selection();
|
||||||
_showIncome = true;
|
ref.read(timeFilterProvider.notifier).set(TimeFilter.lastMonth);
|
||||||
_touchedIndex = -1;
|
},
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _TypeSegment(
|
||||||
|
label: s.expenses,
|
||||||
|
isSelected: !_showIncome,
|
||||||
|
onTap: () {
|
||||||
|
HapticService.selection();
|
||||||
|
setState(() {
|
||||||
|
_showIncome = false;
|
||||||
|
_touchedIndex = -1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: _TypeSegment(
|
||||||
|
label: s.income,
|
||||||
|
isSelected: _showIncome,
|
||||||
|
onTap: () {
|
||||||
|
HapticService.selection();
|
||||||
|
setState(() {
|
||||||
|
_showIncome = true;
|
||||||
|
_touchedIndex = -1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
if (data.isEmpty)
|
if (data.isEmpty)
|
||||||
Expanded(child: _EmptyState(isIncome: _showIncome))
|
Expanded(child: _EmptyState(isIncome: _showIncome))
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
if (_chartType == ChartType.pie)
|
_PieChartSection(
|
||||||
_PieChartCard(
|
|
||||||
data: data,
|
data: data,
|
||||||
total: total,
|
total: total,
|
||||||
touchedIndex: _touchedIndex,
|
touchedIndex: _touchedIndex,
|
||||||
onTouch: (i) => setState(() => _touchedIndex = i),
|
onTouch: (i) => setState(() => _touchedIndex = i),
|
||||||
currencyInfo: currencyInfo,
|
currencyInfo: currencyInfo,
|
||||||
isIncome: _showIncome,
|
isIncome: _showIncome,
|
||||||
)
|
|
||||||
else
|
|
||||||
_BarChartCard(
|
|
||||||
monthlyData: monthlyData,
|
|
||||||
currencyInfo: currencyInfo,
|
|
||||||
isIncome: _showIncome,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 24),
|
||||||
Text(
|
Text(
|
||||||
s.rankedByAmount,
|
s.rankedByAmount,
|
||||||
style: Theme.of(context).textTheme.titleMedium
|
style: Theme.of(context).textTheme.titleMedium
|
||||||
@@ -163,15 +137,13 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
|
|||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 16),
|
||||||
...sortedEntries.asMap().entries.map((entry) {
|
...sortedEntries.map((entry) {
|
||||||
final rank = entry.key + 1;
|
final cat = entry.key;
|
||||||
final cat = entry.value.key;
|
final amount = entry.value;
|
||||||
final amount = entry.value.value;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 10),
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
child: _CategoryRow(
|
child: _CategoryItem(
|
||||||
rank: rank,
|
|
||||||
category: cat,
|
category: cat,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
total: total,
|
total: total,
|
||||||
@@ -192,105 +164,14 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StatsTimeFilter extends StatelessWidget {
|
class _TimeFilterChip extends StatelessWidget {
|
||||||
final AppStrings strings;
|
|
||||||
final TimeFilter timeFilter;
|
|
||||||
final VoidCallback onAllTime;
|
|
||||||
final VoidCallback onMonth;
|
|
||||||
|
|
||||||
const _StatsTimeFilter({
|
|
||||||
required this.strings,
|
|
||||||
required this.timeFilter,
|
|
||||||
required this.onAllTime,
|
|
||||||
required this.onMonth,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
||||||
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
_TimeChip(
|
|
||||||
label: strings.filterAllTime,
|
|
||||||
isSelected: timeFilter == TimeFilter.allTime,
|
|
||||||
isDark: isDark,
|
|
||||||
onTap: () {
|
|
||||||
HapticService.selection();
|
|
||||||
onAllTime();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
_TimeChip(
|
|
||||||
label: strings.filterMonth,
|
|
||||||
isSelected: timeFilter == TimeFilter.lastMonth,
|
|
||||||
isDark: isDark,
|
|
||||||
onTap: () {
|
|
||||||
HapticService.selection();
|
|
||||||
onMonth();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TimeChip extends StatelessWidget {
|
|
||||||
final String label;
|
final String label;
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final bool isDark;
|
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
|
|
||||||
const _TimeChip({
|
const _TimeFilterChip({
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.isDark,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: onTap,
|
|
||||||
child: AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected
|
|
||||||
? AppColors.accent.withOpacity(0.2)
|
|
||||||
: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: isSelected
|
|
||||||
? Border.all(color: AppColors.accent, width: 1.5)
|
|
||||||
: isDark
|
|
||||||
? null
|
|
||||||
: Border.all(color: const Color(0xFFDDDDEE), width: 1),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
|
||||||
color: isSelected
|
|
||||||
? AppColors.accent
|
|
||||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TypeToggle extends StatelessWidget {
|
|
||||||
final String label;
|
|
||||||
final bool isSelected;
|
|
||||||
final Color color;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
|
|
||||||
const _TypeToggle({
|
|
||||||
required this.label,
|
|
||||||
required this.isSelected,
|
|
||||||
required this.color,
|
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -299,22 +180,18 @@ class _TypeToggle extends StatelessWidget {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected ? color : Colors.transparent,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
border: Border.all(
|
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? color
|
? AppColors.accent
|
||||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.2),
|
: Theme.of(context).colorScheme.surface,
|
||||||
width: 1.5,
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Colors.white
|
? Colors.white
|
||||||
@@ -326,48 +203,13 @@ class _TypeToggle extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChartToggle extends StatelessWidget {
|
class _TypeSegment extends StatelessWidget {
|
||||||
final ChartType selected;
|
final String label;
|
||||||
final ValueChanged<ChartType> onChanged;
|
|
||||||
const _ChartToggle({required this.selected, required this.onChanged});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
border: isDark
|
|
||||||
? null
|
|
||||||
: Border.all(color: const Color(0xFFDDDDEE), width: 1),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
_ToggleButton(
|
|
||||||
icon: Icons.pie_chart_rounded,
|
|
||||||
isSelected: selected == ChartType.pie,
|
|
||||||
onTap: () => onChanged(ChartType.pie),
|
|
||||||
),
|
|
||||||
_ToggleButton(
|
|
||||||
icon: Icons.bar_chart_rounded,
|
|
||||||
isSelected: selected == ChartType.bar,
|
|
||||||
onTap: () => onChanged(ChartType.bar),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ToggleButton extends StatelessWidget {
|
|
||||||
final IconData icon;
|
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
const _ToggleButton({
|
|
||||||
required this.icon,
|
const _TypeSegment({
|
||||||
|
required this.label,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
});
|
});
|
||||||
@@ -377,26 +219,35 @@ class _ToggleButton extends StatelessWidget {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? AppColors.accent.withOpacity(0.15)
|
? (_showIncomeColor(label) ? AppColors.income : AppColors.expense)
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Text(
|
||||||
icon,
|
label,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? AppColors.accent
|
? Colors.white
|
||||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||||
size: 20,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _showIncomeColor(String label) {
|
||||||
|
final lower = label.toLowerCase();
|
||||||
|
return lower.contains('income') || lower.contains('доход');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PieChartCard extends ConsumerWidget {
|
class _PieChartSection extends ConsumerWidget {
|
||||||
final Map<String, double> data;
|
final Map<String, double> data;
|
||||||
final double total;
|
final double total;
|
||||||
final int touchedIndex;
|
final int touchedIndex;
|
||||||
@@ -404,7 +255,7 @@ class _PieChartCard extends ConsumerWidget {
|
|||||||
final CurrencyInfo currencyInfo;
|
final CurrencyInfo currencyInfo;
|
||||||
final bool isIncome;
|
final bool isIncome;
|
||||||
|
|
||||||
const _PieChartCard({
|
const _PieChartSection({
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.total,
|
required this.total,
|
||||||
required this.touchedIndex,
|
required this.touchedIndex,
|
||||||
@@ -418,25 +269,10 @@ class _PieChartCard extends ConsumerWidget {
|
|||||||
final s = ref.watch(stringsProvider);
|
final s = ref.watch(stringsProvider);
|
||||||
final fmt = ref.watch(amountFormatProvider);
|
final fmt = ref.watch(amountFormatProvider);
|
||||||
final entries = data.entries.toList();
|
final entries = data.entries.toList();
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
||||||
final accent = isIncome ? AppColors.income : AppColors.expense;
|
final accent = isIncome ? AppColors.income : AppColors.expense;
|
||||||
|
|
||||||
return Container(
|
return SizedBox(
|
||||||
padding: const EdgeInsets.all(20),
|
height: 240,
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(
|
|
||||||
color: isDark
|
|
||||||
? Colors.white.withOpacity(0.08)
|
|
||||||
: const Color(0xFFDDDDEE),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 220,
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -453,8 +289,8 @@ class _PieChartCard extends ConsumerWidget {
|
|||||||
onTouch(response.touchedSection!.touchedSectionIndex);
|
onTouch(response.touchedSection!.touchedSectionIndex);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
sectionsSpace: 3,
|
sectionsSpace: 2,
|
||||||
centerSpaceRadius: 60,
|
centerSpaceRadius: 70,
|
||||||
sections: List.generate(entries.length, (i) {
|
sections: List.generate(entries.length, (i) {
|
||||||
final isTouched = i == touchedIndex;
|
final isTouched = i == touchedIndex;
|
||||||
final cat = entries[i].key;
|
final cat = entries[i].key;
|
||||||
@@ -465,11 +301,11 @@ class _PieChartCard extends ConsumerWidget {
|
|||||||
color: color,
|
color: color,
|
||||||
value: val,
|
value: val,
|
||||||
title: isTouched
|
title: isTouched
|
||||||
? '${(val / total * 100).toStringAsFixed(1)}%'
|
? '${(val / total * 100).toStringAsFixed(0)}%'
|
||||||
: '',
|
: '',
|
||||||
radius: isTouched ? 60 : 50,
|
radius: isTouched ? 55 : 48,
|
||||||
titleStyle: const TextStyle(
|
titleStyle: const TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -483,158 +319,40 @@ 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(
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
|
||||||
context,
|
|
||||||
).colorScheme.onSurface.withOpacity(0.6),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
formatAmount(currencyInfo.symbol, total, fmt),
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
color: accent,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BarChartCard extends ConsumerWidget {
|
|
||||||
final List<MonthlyData> monthlyData;
|
|
||||||
final CurrencyInfo currencyInfo;
|
|
||||||
final bool isIncome;
|
|
||||||
|
|
||||||
const _BarChartCard({
|
|
||||||
required this.monthlyData,
|
|
||||||
required this.currencyInfo,
|
|
||||||
required this.isIncome,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final s = ref.watch(stringsProvider);
|
|
||||||
final fmt = ref.watch(amountFormatProvider);
|
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
||||||
final barColor = isIncome ? AppColors.income : AppColors.expense;
|
|
||||||
final maxY = monthlyData.isEmpty
|
|
||||||
? 100.0
|
|
||||||
: monthlyData.map((e) => e.amount).reduce((a, b) => a > b ? a : b);
|
|
||||||
final adjustedMaxY = maxY * 1.2;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(
|
|
||||||
color: isDark
|
|
||||||
? Colors.white.withOpacity(0.08)
|
|
||||||
: const Color(0xFFDDDDEE),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
s.lastSixMonths,
|
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: BarChart(
|
|
||||||
BarChartData(
|
|
||||||
alignment: BarChartAlignment.spaceAround,
|
|
||||||
maxY: adjustedMaxY > 0 ? adjustedMaxY : 100,
|
|
||||||
barTouchData: BarTouchData(
|
|
||||||
touchTooltipData: BarTouchTooltipData(
|
|
||||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
|
||||||
return BarTooltipItem(
|
|
||||||
formatAmount(currencyInfo.symbol, rod.toY, fmt),
|
|
||||||
const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
titlesData: FlTitlesData(
|
|
||||||
show: true,
|
|
||||||
bottomTitles: AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: true,
|
|
||||||
getTitlesWidget: (value, meta) {
|
|
||||||
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),
|
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(height: 4),
|
||||||
}
|
currencyInfo.code == 'BYN'
|
||||||
return const Text('');
|
? Row(
|
||||||
},
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
BynSign(
|
||||||
|
fontSize: 24,
|
||||||
|
color: accent,
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 2),
|
||||||
leftTitles: const AxisTitles(
|
Text(
|
||||||
sideTitles: SideTitles(showTitles: false),
|
formatAmount('', total, fmt),
|
||||||
),
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
topTitles: const AxisTitles(
|
color: accent,
|
||||||
sideTitles: SideTitles(showTitles: false),
|
fontWeight: FontWeight.w700,
|
||||||
),
|
fontSize: 24,
|
||||||
rightTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(showTitles: false),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
gridData: FlGridData(
|
|
||||||
show: true,
|
|
||||||
drawVerticalLine: false,
|
|
||||||
horizontalInterval: adjustedMaxY > 0 ? adjustedMaxY / 4 : 25,
|
|
||||||
getDrawingHorizontalLine: (value) => FlLine(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
strokeWidth: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
borderData: FlBorderData(show: false),
|
|
||||||
barGroups: List.generate(
|
|
||||||
monthlyData.length,
|
|
||||||
(i) => BarChartGroupData(
|
|
||||||
x: i,
|
|
||||||
barRods: [
|
|
||||||
BarChartRodData(
|
|
||||||
toY: monthlyData[i].amount,
|
|
||||||
color: barColor,
|
|
||||||
width: 24,
|
|
||||||
borderRadius: const BorderRadius.vertical(
|
|
||||||
top: Radius.circular(6),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
formatAmount(currencyInfo.symbol, total, fmt),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
color: accent,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -642,16 +360,14 @@ class _BarChartCard extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CategoryRow extends ConsumerWidget {
|
class _CategoryItem extends ConsumerWidget {
|
||||||
final int rank;
|
|
||||||
final String category;
|
final String category;
|
||||||
final double amount;
|
final double amount;
|
||||||
final double total;
|
final double total;
|
||||||
final CurrencyInfo currencyInfo;
|
final CurrencyInfo currencyInfo;
|
||||||
final bool isIncome;
|
final bool isIncome;
|
||||||
|
|
||||||
const _CategoryRow({
|
const _CategoryItem({
|
||||||
required this.rank,
|
|
||||||
required this.category,
|
required this.category,
|
||||||
required this.amount,
|
required this.amount,
|
||||||
required this.total,
|
required this.total,
|
||||||
@@ -663,89 +379,57 @@ class _CategoryRow 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 isDark = Theme.of(context).brightness == Brightness.dark;
|
|
||||||
final color = AppCategories.colors[category] ?? AppColors.accent;
|
final color = AppCategories.colors[category] ?? AppColors.accent;
|
||||||
final icon = AppCategories.icons[category] ?? Icons.category_rounded;
|
final icon = AppCategories.icons[category] ?? Icons.category_rounded;
|
||||||
final pct = total > 0 ? amount / total : 0.0;
|
final pct = total > 0 ? amount / total : 0.0;
|
||||||
|
|
||||||
return Container(
|
return Row(
|
||||||
padding: const EdgeInsets.all(14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(
|
|
||||||
color: isDark
|
|
||||||
? Colors.white.withOpacity(0.08)
|
|
||||||
: const Color(0xFFDDDDEE),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 28,
|
padding: const EdgeInsets.all(10),
|
||||||
height: 28,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: rank <= 3
|
color: color.withOpacity(0.12),
|
||||||
? 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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withOpacity(0.15),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Icon(icon, color: color, size: 18),
|
child: Icon(icon, color: color, size: 20),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'${(pct * 100).toStringAsFixed(1)}%',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
currencyInfo.code == 'BYN'
|
currencyInfo.code == 'BYN'
|
||||||
? Row(
|
? Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
BynSign(
|
BynSign(
|
||||||
fontSize: 14,
|
fontSize: 15,
|
||||||
color: isIncome
|
color: isIncome ? AppColors.income : AppColors.expense,
|
||||||
? AppColors.income
|
|
||||||
: AppColors.expense,
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
Text(
|
Text(
|
||||||
formatAmount('', amount, fmt),
|
formatAmount('', amount, fmt),
|
||||||
style: Theme.of(context).textTheme.bodyMedium
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
?.copyWith(
|
color: isIncome ? AppColors.income : AppColors.expense,
|
||||||
color: isIncome
|
|
||||||
? AppColors.income
|
|
||||||
: AppColors.expense,
|
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -753,38 +437,12 @@ class _CategoryRow extends ConsumerWidget {
|
|||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
formatAmount(currencyInfo.symbol, amount, fmt),
|
formatAmount(currencyInfo.symbol, amount, fmt),
|
||||||
style: Theme.of(context).textTheme.bodyMedium
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
?.copyWith(
|
color: isIncome ? AppColors.income : AppColors.expense,
|
||||||
color: isIncome
|
|
||||||
? AppColors.income
|
|
||||||
: AppColors.expense,
|
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
|
||||||
'${(pct * 100).toStringAsFixed(1)}%',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onSurface.withOpacity(0.6),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: pct,
|
|
||||||
backgroundColor: Theme.of(context).dividerColor,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
|
||||||
minHeight: 6,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -796,44 +454,30 @@ class _EmptyState extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final s = ref.watch(stringsProvider);
|
final s = ref.watch(stringsProvider);
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Icon(
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: isDark
|
|
||||||
? Colors.white.withOpacity(0.08)
|
|
||||||
: const Color(0xFFDDDDEE),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
Icons.pie_chart_outline_rounded,
|
Icons.pie_chart_outline_rounded,
|
||||||
size: 48,
|
size: 56,
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.3),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
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.withOpacity(0.7),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
isIncome ? s.addIncomeToSeeBreakdown : s.addExpensesToSeeBreakdown,
|
isIncome ? s.addIncomeToSeeBreakdown : s.addExpensesToSeeBreakdown,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
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.4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ class TransactionsNotifier extends AsyncNotifier<List<Transaction>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final transferPairsProvider = Provider<Map<String, Transaction>>((ref) {
|
final transferPairsProvider = Provider<Map<String, Transaction>>((ref) {
|
||||||
final txs = ref.watch(transactionsProvider).value ?? [];
|
final txsAsync = ref.watch(transactionsProvider);
|
||||||
|
final txs = txsAsync.value ?? [];
|
||||||
final transfers = txs.where((t) => t.category == 'Transfer').toList();
|
final transfers = txs.where((t) => t.category == 'Transfer').toList();
|
||||||
final Map<String, Transaction> pairs = {};
|
final Map<String, Transaction> pairs = {};
|
||||||
|
|
||||||
@@ -167,9 +168,9 @@ class _TimeFilterNotifier extends Notifier<TimeFilter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final accountFilteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
final accountFilteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
||||||
|
final activeAccount = ref.watch(activeAccountProvider);
|
||||||
final txsAsync = ref.watch(transactionsProvider);
|
final txsAsync = ref.watch(transactionsProvider);
|
||||||
final txs = txsAsync.value ?? [];
|
final txs = txsAsync.value ?? [];
|
||||||
final activeAccount = ref.watch(activeAccountProvider);
|
|
||||||
|
|
||||||
if (activeAccount == null) {
|
if (activeAccount == null) {
|
||||||
return txs;
|
return txs;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class _AccountEditorPanelState extends ConsumerState<AccountEditorPanel> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -76,7 +76,7 @@ class _AccountEditorPanelState extends ConsumerState<AccountEditorPanel> {
|
|||||||
).colorScheme.onSurface.withOpacity(0.6),
|
).colorScheme.onSurface.withOpacity(0.6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 6),
|
||||||
IntrinsicHeight(
|
IntrinsicHeight(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
Reference in New Issue
Block a user