Skip to content

Commit

Permalink
v1.0.20 - Currency Service only when required (#20)
Browse files Browse the repository at this point in the history
* v1.0.20 - Update Currency Service

* v1.0.20 - Currency Service only when multi currency

* v1.0.20 - Show error dialog

* v1.0.20 - More error dialogs

* v1.0.20 - Move

* v1.0.20 - Show currency in Months

* v1.0.20 - Month indicator
  • Loading branch information
Donnie committed Jan 21, 2024
1 parent c0fc9e7 commit 5fc3de4
Show file tree
Hide file tree
Showing 15 changed files with 216 additions and 150 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<a href="https://flutter.dev/" style="text-decoration:none" area-label="flutter">
<img src="https://img.shields.io/badge/Platform-Flutter%203.16.5-blue">
</a>
<a href="https://github.com/Donnie/Finease/releases/tag/v1.0.19" style="text-decoration:none" area-label="flutter">
<img src="https://img.shields.io/badge/Version-1.0.19-orange">
<a href="https://github.com/Donnie/Finease/releases/tag/v1.0.20" style="text-decoration:none" area-label="flutter">
<img src="https://img.shields.io/badge/Version-1.0.20-orange">
</a>
<a href="https://github.com/Donnie/Finease/actions/workflows/android_release.yml" style="text-decoration:none" area-label="flutter">
<img src="https://github.com/Donnie/Finease/actions/workflows/android_release.yml/badge.svg">
Expand Down
36 changes: 25 additions & 11 deletions lib/core/currency.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,25 @@ class ExchangeService {
'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml';

Future<Map<String, double>> getExchangeRateMap(String baseCurrency) async {
final response = await http.get(Uri.parse(url));
if (response.statusCode != 200) {
throw Exception('Failed to load currency data');
}
try {
final response =
await http.get(Uri.parse(url)).timeout(const Duration(seconds: 5));
if (response.statusCode != 200) {
throw InternetUnavailableError('Failed to load currency data');
}

final document = XmlDocument.parse(response.body);
final rates = _parseRates(document);
final document = XmlDocument.parse(response.body);
final rates = _parseRates(document);

if (!rates.containsKey(baseCurrency)) {
throw Exception('Base currency not found');
}
if (!rates.containsKey(baseCurrency)) {
throw Exception('Base currency not found');
}

final baseRate = rates[baseCurrency]!;
return rates.map((currency, rate) => MapEntry(currency, rate / baseRate));
final baseRate = rates[baseCurrency]!;
return rates.map((currency, rate) => MapEntry(currency, rate / baseRate));
} catch (e) {
throw InternetUnavailableError('Failed to load currency data');
}
}

Map<String, double> _parseRates(XmlDocument document) {
Expand All @@ -41,3 +46,12 @@ class ExchangeService {
return rates;
}
}

class InternetUnavailableError implements Exception {
final String message;

InternetUnavailableError(this.message);

@override
String toString() => 'InternetUnavailableError: $message';
}
36 changes: 20 additions & 16 deletions lib/db/accounts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -165,35 +165,39 @@ class AccountService {
// Execute the query
List<Map<String, dynamic>> result = await dbClient.rawQuery(sql);

int totalBalance = 0;
// result is like:
// [{total_balance: 9880057, currency: EUR}]

int totalBalance = 0;
String prefCurrency =
await _settingService.getSetting(Setting.prefCurrency);

// Determine if conversion is needed and initialize currencyBoxService once if true
bool needsConversion = result.any((row) => row['currency'] != prefCurrency);
if (needsConversion) {
await currencyBoxService.init();
}

// Loop through each currency and convert the balance to preferred currency
await currencyBoxService.init();
for (var row in result) {
String currency = row['currency'];
int balance = row['total_balance'];

// Convert balance to preferred currency
double convertedBalance =
await _convertCurrency(currency, balance, prefCurrency);
totalBalance += convertedBalance.round();
// If the currency is not the preferred currency, convert it
if (currency != prefCurrency) {
double rate = await currencyBoxService.getSingleRate(
currency,
prefCurrency,
);
totalBalance += (balance * rate).round();
} else {
// Already in preferred currency, no conversion needed
totalBalance += balance;
}
}

return totalBalance / 100;
}

Future<double> _convertCurrency(
String fromCurrency,
int balance,
String toCurrency,
) async {
double rate =
await currencyBoxService.getSingleRate(fromCurrency, toCurrency);
return balance * rate;
}
}

enum AccountType {
Expand Down
63 changes: 34 additions & 29 deletions lib/db/currency.dart
Original file line number Diff line number Diff line change
@@ -1,59 +1,64 @@
import 'package:finease/core/export.dart';
import 'package:finease/db/settings.dart';
import 'package:hive/hive.dart';

class CurrencyBoxService {
static const String _boxName = 'currencies';
late Box _box;
final ExchangeService _currencyService = ExchangeService();
late String prefCurrency;
final ExchangeService _exchangeService = ExchangeService();

Future<void> init() async {
_box = await Hive.openBox(_boxName);
}

Future<Map<String, double>> getLatestRates(String baseCurrency) async {
final currentDate = DateTime.now();
final lastUpdate = _box.get('lastUpdate') as DateTime?;
prefCurrency = await SettingService().getSetting(Setting.prefCurrency);

if (lastUpdate == null || lastUpdate.day != currentDate.day) {
await _updateRates(baseCurrency);
// Check if data is older than a day or if no data is available (lastUpdate is null)
if (lastUpdate == null ||
lastUpdate.difference(currentDate).inDays.abs() >= 1) {
try {
await _updateRates(prefCurrency);
} on InternetUnavailableError {
if (lastUpdate == null) {
// If no data available and there is an InternetUnavailableError, then rethrow it
rethrow;
}
// else, ignore the error because we have data (even if it is outdated)
} catch (e) {
// Handle all other exceptions or rethrow as needed
rethrow;
}
}

return Map<String, double>.from(_box.toMap())
..remove('lastUpdate'); // Exclude the 'lastUpdate' key
}

Future<double> getSingleRate(
String baseCurrency,
String targetCurrency,
) async {
final currentDate = DateTime.now();
final lastUpdate = _box.get('lastUpdate') as DateTime?;

if (lastUpdate == null || lastUpdate.day != currentDate.day) {
await _updateRates(baseCurrency);
double baseRate = 1.0;
double targetRate = 1.0;
// Assuming data is always available and up-to-date.
// Retrieve the rates directly from the box.
if (baseCurrency != prefCurrency) {
baseRate = _box.get(baseCurrency) ?? 0;
}

// Retrieve the rate for the targetCurrency and the baseCurrency
final targetRate = _box.get(targetCurrency) as double?;
final baseRate = _box.get(baseCurrency) as double?;

// Check if either rate is null
if (targetRate == null) {
throw Exception('Rate for $targetCurrency not found');
}
if (baseRate == null) {
throw Exception('Rate for $baseCurrency not found');
if (targetCurrency != prefCurrency) {
targetRate = _box.get(targetCurrency) ?? 0;
}

// Calculate the combined rate
final combinedRate = targetRate / baseRate;
// rates must be available; if not, throw an exception.
if (targetRate == 0 || baseRate == 0) {
throw Exception('Unable to find rate for $baseCurrency/$targetCurrency');
}

return combinedRate;
// Calculate and return the combined rate.
return targetRate / baseRate;
}

Future<void> _updateRates(String baseCurrency) async {
try {
final rates = await _currencyService.getExchangeRateMap(baseCurrency);
final rates = await _exchangeService.getExchangeRateMap(baseCurrency);
await _box.putAll(rates);
await _box.put('lastUpdate', DateTime.now());
} catch (e) {
Expand Down
30 changes: 27 additions & 3 deletions lib/db/months.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class MonthService {
AND e.date BETWEEN months.startDate AND months.endDate
AND ac.currency = '$prefCurrency'
AND ad.currency = '$prefCurrency'
), 0) AS expense
), 0) AS expense,
'$prefCurrency' AS currency
FROM (
SELECT
REPLACE(DATETIME(monthDate, 'start of month'), ' ', 'T') || 'Z' as startDate,
Expand All @@ -61,15 +62,17 @@ class MonthService {
income,
expense,
(income - expense) as effect,
SUM(income - expense) OVER (ORDER BY startDate ASC) as networth
SUM(income - expense) OVER (ORDER BY startDate ASC) as networth,
currency
FROM MonthlyTotals
)
SELECT
date,
effect,
expense,
income,
networth
networth,
currency
FROM CumulativeTotals;
''');

Expand All @@ -87,13 +90,15 @@ class Month {
num? expense;
num? income;
num? networth;
String? currency;

Month({
this.date,
this.effect,
this.expense,
this.income,
this.networth,
this.currency,
});

factory Month.fromJson(Map<String, dynamic> json) {
Expand All @@ -103,6 +108,25 @@ class Month {
expense: json['expense'] / 100,
income: json['income'] / 100,
networth: json['networth'] / 100,
currency: json['currency'],
);
}

// Calculate the factor based on the relationship between income and expense
double get factor {
if (income == null || expense == null) {
return 0; // or some default value or handle error
}
// Ensure that neither income nor expense is zero to avoid division by zero
if (income == 0 && expense == 0) {
return 0.5; // When both are zero, we can define factor as 0.5
}

// Bias towards extremes for visibility
num incomeSquared = income! * income!;
num expenseSquared = expense! * expense!;
return (incomeSquared / (incomeSquared + expenseSquared));
}

bool get good => factor > 0.5;
}
11 changes: 9 additions & 2 deletions lib/pages/add_account/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:finease/core/export.dart';
import 'package:finease/db/accounts.dart';
import 'package:finease/pages/export.dart';
import 'package:finease/parts/error_dialog.dart';
import 'package:finease/parts/export.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
Expand Down Expand Up @@ -79,8 +80,14 @@ class AddAccountScreenState extends State<AddAccountScreen> {
liquid: accountLiquid,
type: accountType,
);
account = await _accountService.createAccount(account);
widget.onFormSubmitted(account);
try {
account = await _accountService.createAccount(account);
widget.onFormSubmitted(account);
} catch (e) {
_showError(e);
}
}
}

Future<void> _showError(e) async => showErrorDialog(e.toString(), context);
}
17 changes: 12 additions & 5 deletions lib/pages/add_entry/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:finease/db/accounts.dart';
import 'package:finease/db/entries.dart';
import 'package:finease/pages/export.dart';
import 'package:finease/parts/error_dialog.dart';
import 'package:finease/parts/export.dart';
import 'package:finease/routes/routes_name.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -104,12 +105,18 @@ class AddEntryScreenState extends State<AddEntryScreen> {
notes: entryNotes,
date: _dateTime,
);
if (_debitAccount!.currency != _creditAccount!.currency) {
await _entryService.createForexEntry(entry);
} else {
await _entryService.createEntry(entry);
try {
if (_debitAccount!.currency != _creditAccount!.currency) {
await _entryService.createForexEntry(entry);
} else {
await _entryService.createEntry(entry);
}
widget.onFormSubmitted();
} catch (e) {
_showError(e);
}
widget.onFormSubmitted();
}
}

Future<void> _showError(e) async => showErrorDialog(e.toString(), context);
}
4 changes: 2 additions & 2 deletions lib/pages/export.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export "home/frame/destinations.dart";
export "home/frame/main.dart";
export "home/frame/mobile.dart";
export "home/frame/tablet.dart";
export 'home/months/screen/main.dart';
export 'home/months/screen/month_card.dart';
export 'home/months/main.dart';
export 'home/months/month_card.dart';
export 'home/summary/main.dart';
export 'home/summary/widgets.dart';
export "intro/intro_big.dart";
Expand Down
Loading

0 comments on commit 5fc3de4

Please sign in to comment.