Skip to content

Commit

Permalink
[#534] [BF] [Linux] Exchange inconsistency. Fix collisions
Browse files Browse the repository at this point in the history
  • Loading branch information
lyskouski committed Dec 16, 2024
1 parent cbfe076 commit 3d85457
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 112 deletions.
170 changes: 69 additions & 101 deletions lib/_classes/controller/exchange_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,21 @@ import 'package:app_finance/_ext/double_ext.dart';
import 'package:flutter/material.dart';
import 'package:flutter_currency_picker/flutter_currency_picker.dart';

typedef ExchangeMap = Map<String, List<TextEditingController>>;

typedef ExchangeScope = ({
String from,
String to,
CurrencyAppData currency,
TextEditingController rate,
TextEditingController sum,
});

typedef ExchangeMap = Map<String, ExchangeScope>;

class ExchangeController extends ValueNotifier<ExchangeMap> {
final Map<String, CurrencyAppData> rate = {};
final List<String> pairs = [];
final AppData store;
final TextEditingController targetController;
List<Currency?> source;
Currency? target;
List<Currency?> source;

ExchangeController(
super.value, {
Expand All @@ -35,135 +34,104 @@ class ExchangeController extends ValueNotifier<ExchangeMap> {
targetController.addListener(_updateAll);
}

int get length => value.length;

@override
void dispose() {
clear();
super.dispose();
}

void clear() {
for (var scope in value.values) {
scope.rate.dispose();
scope.sum.dispose();
}
value.clear();
}

void restate(List<Currency?> source, Currency? target) {
this.source = source;
this.target = target;
clear();

if (source.isEmpty) {
return;
}
_add(target?.code, source.first?.code);
for (int i = 0; i < source.length - 1; i++) {
_add(source[i]?.code, source[i + 1]?.code);
}
notifyListeners();
}

ExchangeScope get(int index) {
final uuid = pairs[index];
final keys = uuid.split('-');
return (
from: keys[0],
to: keys[1],
rate: value[uuid]![0],
sum: value[uuid]![1],
);
}

void save() {
for (var uuid in pairs) {
if (rate[uuid] == null || rate[uuid]!.details == 1.0) {
for (int i = 0; i < source.length; i++) {
final uuid = [target, source[i]].map((v) => v?.code ?? '?').toList().join('-');
if (target == null || source[i] == null || target.code == source[i]!.code || value[uuid] != null) {
continue;
}
store.update(uuid, rate[uuid]!, true);
}
}

void clear() {
for (var scope in value.values) {
for (int i = 0; i < source.length - 1; i++) {
scope[i].dispose();
}
final currency = store.getByUuid(uuid) ?? CurrencyAppData(currency: target, currencyFrom: source[i]);
final amount = (double.tryParse(targetController.text) ?? 0.0) * currency.details;
value[uuid] = (
from: target.code,
to: source[i]!.code,
currency: currency,
rate: TextEditingController(text: currency.details.toString())..addListener(() => _updateSum(uuid)),
sum: TextEditingController(text: amount.toString())..addListener(() => _updateRate(uuid)),
);
}
value.clear();
rate.clear();
pairs.clear();
notifyListeners();
}

@override
void dispose() {
clear();
super.dispose();
}
ExchangeScope get(int index) => value.values.elementAt(index);

void _add(String? from, String? to) {
final uuid = [from, to].map((v) => v ?? '?').toList().join('-');
pairs.add(uuid);
rate[uuid] ??= store.getByUuid(uuid) ??
CurrencyAppData(
currency: CurrencyProvider.find(to),
currencyFrom: CurrencyProvider.find(from),
);
value[uuid] ??= [
TextEditingController(text: rate[uuid]!.details.toString())..addListener(() => _updateSum(uuid)),
TextEditingController(text: _getAmount(uuid)?.toString())..addListener(() => _updateRate(uuid)),
];
void save() {
for (var item in value.values) {
double rate = double.tryParse(item.rate.text) ?? 0.0;
item.currency.details = rate;
store.update(item.currency.uuid, item.currency, true);
}
}

void _updateAll() {
for (String uuid in pairs) {
for (var uuid in value.keys) {
_updateSum(uuid);
}
}

int _findDecimals(String? value) {
return (value ?? '').contains('.') ? value!.split('.')[1].length : 0;
}

int? _getDecimals(String uuid) {
int? decimals = CurrencyProvider.find(uuid.split('-')[1])?.decimalDigits;
if (rate[uuid]!.details != null) {
final rateString = rate[uuid]!.details.toString();
final rateDecimals = rateString.contains('.') ? rateString.split('.')[1].length : 0;
if (rateDecimals > (decimals ?? 0)) {
decimals = rateDecimals;
}
int decimals = CurrencyProvider.find(uuid.split('-')[1])?.decimalDigits ?? 0;
final rateDecimals = _findDecimals(value[uuid]?.rate.text);
if (rateDecimals > decimals) {
decimals = rateDecimals;
}
final sumDecimals = _findDecimals(value[uuid]?.sum.text);
if (sumDecimals > decimals) {
decimals = sumDecimals;
}
return decimals;
}

void _updateSum(String uuid) {
List<TextEditingController> pair = value[uuid]!;
if (pair[0].text != '' && rate[uuid] != null) {
rate[uuid]!.details = double.tryParse(pair[0].text);
}
final amount = _getAmount(uuid);
final current = double.tryParse(pair[1].text);
ExchangeScope pair = value[uuid]!;
double rate = double.tryParse(pair.rate.text) ?? 0.0;
double amount = (double.tryParse(targetController.text) ?? 0.0) * rate;
double? current = double.tryParse(pair.sum.text);
final decimals = _getDecimals(uuid);
if (amount?.toFixed(decimals) != current?.toFixed(decimals)) {
pair[1].text = (amount ?? '').toString();
pair[1].notifyListeners();
if (targetController.text != '' && amount.toFixed(decimals).isNotEqual(current?.toFixed(decimals))) {
pair.sum.text = amount.toString();
// pair.sum.notifyListeners();
}
}

void _updateRate(String uuid) {
List<TextEditingController> pair = value[uuid]!;
final sum = _getRate(uuid, double.tryParse(pair[1].text));
final current = double.tryParse(pair[0].text);
ExchangeScope pair = value[uuid]!;
double rate = double.tryParse(pair.rate.text) ?? 0.0;
double val = double.tryParse(targetController.text) ?? 0.0;
double amount = double.tryParse(pair.sum.text) ?? 0.0;
double newRate = val > 0 ? amount / val : 0.0;
final decimals = _getDecimals(uuid);
if (sum?.toFixed(decimals) != current?.toFixed(decimals)) {
if (sum != null && rate[uuid] != null) {
rate[uuid]!.details = sum;
}
pair[0].text = (sum ?? '').toString();
pair[0].notifyListeners();
}
}

double? _getRate(String uuid, double? amount) {
if (amount == null) return null;
double targetAmount = double.tryParse(targetController.text) ?? 0;
final index = pairs.indexOf(uuid);
double prev = index > 0 ? double.tryParse(value[pairs[index - 1]]?[1].text ?? '0') ?? 0 : targetAmount;
return prev > 0 ? amount / prev : null;
}

double? _getAmount(String uuid) {
double? result = rate[uuid]?.details;
double? input = double.tryParse(targetController.text);
if (result != null && input != null) {
final index = pairs.indexOf(uuid);
double amount = index > 0 ? double.tryParse(value[pairs[index - 1]]?[1].text ?? '0') ?? 0 : input;
result *= amount;
} else {
result = null;
if (targetController.text != '' && rate.toFixed(decimals).isNotEqual(newRate.toFixed(decimals))) {
pair.rate.text = newRate.toString();
// pair.rate.notifyListeners();
}
return result;
}
}
4 changes: 4 additions & 0 deletions lib/_ext/double_ext.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ import 'dart:math' as math;

extension IntExt on double {
double toFixed(int? digits) => (this * math.pow(10, digits ?? 8)).round() / math.pow(10, digits ?? 8);

bool isEqual(double? value) => (this - (value ?? 0)).abs() < 1e-10;

bool isNotEqual(double? value) => !isEqual(value);
}
5 changes: 1 addition & 4 deletions lib/design/form/currency_exchange_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,8 @@ class CurrencyExchangeInputState extends State<CurrencyExchangeInput> {
}
final TextTheme textTheme = context.textTheme;
return Column(
children: List<Widget>.generate(widget.source.length, (index) {
children: List<Widget>.generate(widget.controller.length, (index) {
final scope = widget.controller.get(index);
if (source[index] == null || scope.from == scope.to) {
return ThemeHelper.emptyBox;
}
return Column(
mainAxisAlignment: AppDesign.getAlignment<MainAxisAlignment>(),
children: [
Expand Down
8 changes: 4 additions & 4 deletions lib/pages/start/widgets/budget_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ class BudgetTabState extends BudgetAddPageState<BudgetTab> {
}
updateStorage();
AppPreferences.set(AppPreferences.prefBudget, super.state.getList(AppDataType.budgets).first?.uuid);
(widget as BudgetTab).setState();
widget.setState();
});
}

Widget _nextButton(BuildContext context, BoxConstraints constraints) => FullSizedButtonWidget(
constraints: constraints,
controller: focus,
onPressed: () => (widget as BudgetTab).setState(),
onPressed: () => widget.setState(),
title: AppLocale.labels.goNextTooltip,
icon: Icons.exit_to_app_rounded,
);
Expand All @@ -55,9 +55,9 @@ class BudgetTabState extends BudgetAddPageState<BudgetTab> {
return Padding(
padding: EdgeInsets.only(top: ThemeHelper.getIndent(2)),
child: LayoutBuilder(builder: (context, constraints) {
if ((widget as BudgetTab).isFirstBoot) {
if (widget.isFirstBoot) {
WidgetsBinding.instance.addPostFrameCallback((_) {
(widget as BudgetTab).setButton(
widget.setButton(
isCreated ? _nextButton(context, constraints) : buildButton(context, constraints),
);
});
Expand Down
6 changes: 3 additions & 3 deletions test/unit/_classes/controller/exchange_controller_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ void main() {
setUp(() => CurrencyDefaults.cache = MockSharedPreferences());

test('_updateSum | _updateRate', () {
final editor = TextEditingController(text: '10');
final editor = TextEditingController(text: '123.02');
final controller = ExchangeController(
{},
store: WrapperMockAppData(),
source: [CurrencyProvider.find('USD')],
source: [CurrencyProvider.find('USD'), CurrencyProvider.find('EUR')],
target: CurrencyProvider.find('EUR'),
targetController: editor,
);
expect(controller.length, 1);
expect(controller.get(0).from, 'EUR');
expect(controller.get(0).to, 'USD');
editor.text = '123.02';
expect(controller.get(0).rate.text, '1.0');
expect(controller.get(0).sum.text, '123.02');
controller.get(0).rate.text = '2.0';
Expand Down

0 comments on commit 3d85457

Please sign in to comment.