This commit is contained in:
2026-03-20 17:38:04 +03:00
parent a6d393332a
commit 42db4814be
3 changed files with 120 additions and 5 deletions
@@ -10,6 +10,8 @@ class AddTransactionState {
final String note; final String note;
final bool isSubmitting; final bool isSubmitting;
final String? editingId; final String? editingId;
final String overrideCurrency;
final String overrideCurrencyCode;
const AddTransactionState({ const AddTransactionState({
this.amount, this.amount,
@@ -19,6 +21,8 @@ class AddTransactionState {
this.note = '', this.note = '',
this.isSubmitting = false, this.isSubmitting = false,
this.editingId, this.editingId,
this.overrideCurrency = '\$',
this.overrideCurrencyCode = 'USD',
}); });
factory AddTransactionState.fromTransaction(Transaction tx) { factory AddTransactionState.fromTransaction(Transaction tx) {
@@ -29,6 +33,8 @@ class AddTransactionState {
date: tx.date, date: tx.date,
note: tx.note ?? '', note: tx.note ?? '',
editingId: tx.id, editingId: tx.id,
overrideCurrency: tx.currency,
overrideCurrencyCode: tx.currencyCode,
); );
} }
@@ -44,6 +50,8 @@ class AddTransactionState {
String? note, String? note,
bool? isSubmitting, bool? isSubmitting,
String? editingId, String? editingId,
String? overrideCurrency,
String? overrideCurrencyCode,
}) => }) =>
AddTransactionState( AddTransactionState(
amount: amount ?? this.amount, amount: amount ?? this.amount,
@@ -53,6 +61,8 @@ class AddTransactionState {
note: note ?? this.note, note: note ?? this.note,
isSubmitting: isSubmitting ?? this.isSubmitting, isSubmitting: isSubmitting ?? this.isSubmitting,
editingId: editingId ?? this.editingId, editingId: editingId ?? this.editingId,
overrideCurrency: overrideCurrency ?? this.overrideCurrency,
overrideCurrencyCode: overrideCurrencyCode ?? this.overrideCurrencyCode,
); );
bool get isEditing => editingId != null; bool get isEditing => editingId != null;
@@ -80,6 +90,10 @@ class AddTransactionNotifier extends StateNotifier<AddTransactionState> {
void setSubmitting(bool v) => state = state.copyWith(isSubmitting: v); void setSubmitting(bool v) => state = state.copyWith(isSubmitting: v);
void setCurrency(String symbol, String code) {
state = state.copyWith(overrideCurrency: symbol, overrideCurrencyCode: code);
}
void reset() => state = AddTransactionState.empty(); void reset() => state = AddTransactionState.empty();
} }
+88 -5
View File
@@ -39,6 +39,12 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen> {
if (widget.initial != null) { if (widget.initial != null) {
_amountController.text = widget.initial!.amount.toString(); _amountController.text = widget.initial!.amount.toString();
_noteController.text = widget.initial!.note ?? ''; _noteController.text = widget.initial!.note ?? '';
} else {
// Set default currency from global provider after first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
final curr = ref.read(currencyProvider);
ref.read(addTransactionProvider(null).notifier).setCurrency(curr.symbol, curr.code);
});
} }
} }
@@ -53,7 +59,6 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen> {
Future<void> _submit() async { Future<void> _submit() async {
if (!_formKey.currentState!.validate()) return; if (!_formKey.currentState!.validate()) return;
final state = ref.read(addTransactionProvider(widget.initial)); final state = ref.read(addTransactionProvider(widget.initial));
final currencyInfo = ref.read(currencyProvider);
ref.read(addTransactionProvider(widget.initial).notifier).setSubmitting(true); ref.read(addTransactionProvider(widget.initial).notifier).setSubmitting(true);
final note = _noteController.text.trim(); final note = _noteController.text.trim();
@@ -65,8 +70,8 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen> {
type: state.type, type: state.type,
date: state.date, date: state.date,
note: note.isEmpty ? null : note, note: note.isEmpty ? null : note,
currency: currencyInfo.symbol, currency: state.overrideCurrency,
currencyCode: currencyInfo.code, currencyCode: state.overrideCurrencyCode,
); );
if (state.isEditing) { if (state.isEditing) {
@@ -105,7 +110,7 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = ref.watch(addTransactionProvider(widget.initial)); final state = ref.watch(addTransactionProvider(widget.initial));
final categories = ref.watch(availableCategoriesProvider(widget.initial)); final categories = ref.watch(availableCategoriesProvider(widget.initial));
final currencyInfo = ref.watch(currencyProvider); final overrideCurrency = state.overrideCurrency;
final isDark = Theme.of(context).brightness == Brightness.dark; final isDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
@@ -181,7 +186,7 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen> {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text( child: Text(
currencyInfo.symbol, overrideCurrency,
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -226,6 +231,22 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen> {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Currency
Text(
'Currency',
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
const SizedBox(height: 8),
_CurrencyPicker(
selected: state.overrideCurrencyCode,
onChanged: (symbol, code) =>
ref.read(addTransactionProvider(widget.initial).notifier).setCurrency(symbol, code),
),
const SizedBox(height: 20),
// Category // Category
_SectionLabel('Category'), _SectionLabel('Category'),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -484,3 +505,65 @@ class _CategoryPicker extends StatelessWidget {
); );
} }
} }
class _CurrencyPicker extends StatelessWidget {
final String selected;
final void Function(String symbol, String code) onChanged;
const _CurrencyPicker({
required this.selected,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
final currencies = [
('USD', '\$'),
('EUR', ''),
('BYN', 'Br'),
('RUB', ''),
];
final colorScheme = Theme.of(context).colorScheme;
return Row(
children: currencies.map((c) {
final isSelected = c.$1 == selected;
return Expanded(
child: GestureDetector(
onTap: () => onChanged(c.$2, c.$1),
child: Container(
margin: EdgeInsets.only(right: c.$1 == currencies.last.$1 ? 0 : 8),
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF7C6DED).withOpacity(0.15) : Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isSelected ? const Color(0xFF7C6DED) : Colors.transparent,
width: 1.5,
),
),
child: Column(
children: [
Text(
c.$2,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isSelected ? const Color(0xFF7C6DED) : colorScheme.onSurface,
),
),
Text(
c.$1,
style: TextStyle(
fontSize: 10,
color: colorScheme.onSurface.withOpacity(0.5),
),
),
],
),
),
),
);
}).toList(),
);
}
}
+18
View File
@@ -195,6 +195,7 @@ class _SearchBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return TextField( return TextField(
controller: controller, controller: controller,
focusNode: focusNode, focusNode: focusNode,
@@ -212,6 +213,23 @@ class _SearchBar extends StatelessWidget {
}, },
) )
: null, : null,
filled: true,
fillColor: Theme.of(context).inputDecorationTheme.fillColor,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: isDark ? Colors.transparent : const Color(0xFFCCCCDD),
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF7C6DED), width: 1.5),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
), ),
onChanged: (v) => ref.read(searchQueryProvider.notifier).state = v, onChanged: (v) => ref.read(searchQueryProvider.notifier).state = v,