invoice/lib/utils/formatting.dart

352 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:intl/intl.dart';
import 'package:intl/number_symbols_data.dart';
import 'package:intl/number_symbols.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/group_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/company/company_selectors.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
double round(double value, int precision) {
if (value == null || value.isNaN) {
return 0;
}
final int fac = pow(10, precision);
return (value * fac).round() / fac;
}
int parseInt(String value, {bool zeroIsNull = false}) {
value = value.replaceAll(RegExp(r'[^0-9\.\-]'), '');
final intValue = int.tryParse(value) ?? 0;
return (intValue == 0 && zeroIsNull) ? null : intValue;
}
double parseDouble(String value, {bool zeroIsNull = false}) {
// check for comma as decimal separator
final RegExp regExp = RegExp(r',[\d]{1,2}$');
if (regExp.hasMatch(value)) {
value = value.replaceAll('.', '');
value = value.replaceAll(',', '.');
}
value = value.replaceAll(RegExp(r'[^0-9\.\-]'), '');
final doubleValue = double.tryParse(value) ?? 0.0;
return (doubleValue == 0 && zeroIsNull) ? null : doubleValue;
}
enum FormatNumberType {
money, // $1,000.00
percent, // 1,000.00%
int, // 1,000
double, // 1,000.00
inputMoney, // 1000.00
inputAmount, // 1000
duration,
}
String formatNumber(
double value,
BuildContext context, {
String clientId,
String currencyId,
FormatNumberType formatNumberType = FormatNumberType.money,
bool showCurrencyCode,
bool zeroIsNull = false,
bool roundToPrecision = true,
}) {
if ((zeroIsNull ||
formatNumberType == FormatNumberType.inputMoney ||
formatNumberType == FormatNumberType.inputAmount) &&
value == 0) {
return null;
} else if (value == null) {
return '';
}
if (formatNumberType == FormatNumberType.duration) {
return formatDuration(Duration(seconds: value.toInt()));
}
final state = StoreProvider.of<AppState>(context).state;
final CompanyEntity company = state.company;
final ClientEntity client = state.clientState.map[clientId];
final GroupEntity group = state.groupState.map[client?.groupId];
String countryId;
if (client != null && client.hasNameSet) {
countryId = client.countryId;
} else {
countryId = company.settings.countryId;
}
if (currencyId == kCurrencyAll) {
currencyId = company.currencyId;
} else if (currencyId != null && currencyId.isNotEmpty) {
// do nothing
} else if (client != null && client.hasCurrency) {
currencyId = client.currencyId;
} else if (group != null && group.hasCurrency) {
currencyId = group.currencyId;
} else {
currencyId = company.currencyId;
}
final CurrencyEntity currency = state.staticState.currencyMap[currencyId];
final CountryEntity country =
state.staticState.countryMap[countryId] ?? CountryEntity();
if (currency == null) {
return '';
}
if (formatNumberType == FormatNumberType.money) {
value = round(value, currency.precision);
}
String thousandSeparator = currency.thousandSeparator;
String decimalSeparator = currency.decimalSeparator;
bool swapCurrencySymbol = currency.swapCurrencySymbol;
if (currency.id == kCurrencyEuro) {
swapCurrencySymbol = country.swapCurrencySymbol;
if (country.thousandSeparator != null &&
country.thousandSeparator.isNotEmpty) {
thousandSeparator = country.thousandSeparator;
}
if (country.decimalSeparator != null &&
country.decimalSeparator.isNotEmpty) {
decimalSeparator = country.decimalSeparator;
}
}
numberFormatSymbols['custom'] = NumberSymbols(
NAME: 'custom',
DECIMAL_SEP: decimalSeparator,
GROUP_SEP: thousandSeparator,
ZERO_DIGIT: '0',
PLUS_SIGN: '+',
MINUS_SIGN: '-',
);
NumberFormat formatter;
String formatted;
if (formatNumberType == FormatNumberType.int) {
return NumberFormat('#,##0', 'custom').format(value);
} else if (formatNumberType == FormatNumberType.double) {
return NumberFormat('#,##0.#####', 'custom').format(value);
} else if (formatNumberType == FormatNumberType.inputAmount) {
return NumberFormat('#.#####', 'custom').format(value);
} else if (formatNumberType == FormatNumberType.inputMoney) {
if (currency.precision == 0) {
return NumberFormat('#.#####', 'custom').format(value);
} else if (currency.precision == 1) {
return NumberFormat('#.0####', 'custom').format(value);
} else if (currency.precision == 2) {
return NumberFormat('#.00###', 'custom').format(value);
} else if (currency.precision == 3) {
return NumberFormat('#.000##', 'custom').format(value);
}
} else {
if (formatNumberType == FormatNumberType.percent) {
formatter = NumberFormat('#,##0.#####', 'custom');
} else if (currency.precision == 0) {
formatter = NumberFormat('#,##0.#####', 'custom');
} else if (currency.precision == 1) {
formatter = NumberFormat('#,##0.0####', 'custom');
} else if (currency.precision == 2) {
formatter = NumberFormat('#,##0.00###', 'custom');
} else if (currency.precision == 3) {
formatter = NumberFormat('#,##0.000##', 'custom');
}
formatted = formatter.format(value < 0 ? value * -1 : value);
}
if (formatNumberType == FormatNumberType.percent) {
return '$formatted%';
} else if ((showCurrencyCode ?? company.settings.showCurrencyCode ?? false) ||
currency.symbol.isEmpty) {
return '$formatted ${currency.code}';
} else if (swapCurrencySymbol) {
return '$formatted ${currency.symbol.trim()}';
} else if (value < 0) {
return '${currency.symbol}$formatted';
} else {
return '${currency.symbol}$formatted';
}
}
String cleanPhoneNumber(String phoneNumber) {
return phoneNumber.replaceAll(RegExp(r'\D'), '');
}
String formatURL(String url) {
if (url.startsWith('http')) {
return url;
}
return 'http://' + url;
}
String formatAddress(
{dynamic object, bool isShipping = false, String delimiter = '\n'}) {
var str = '';
final String address1 =
(isShipping ? object.shippingAddress1 : object.address1) ?? '';
final String address2 =
(isShipping ? object.shippingAddress2 : object.address2) ?? '';
final String city = (isShipping ? object.shippingCity : object.city) ?? '';
final String state = (isShipping ? object.shippingState : object.state) ?? '';
final String postalCode =
(isShipping ? object.shippingPostalCode : object.postalCode) ?? '';
if (address1.isNotEmpty) {
str += address1 + delimiter;
}
if (address2.isNotEmpty) {
str += address2 + delimiter;
}
if (city.isNotEmpty || state.isNotEmpty || postalCode.isNotEmpty) {
str += city + ',' + state + ' ' + postalCode;
}
return str;
}
String convertDateTimeToSqlDate([DateTime date]) {
date = date ?? DateTime.now();
return date.toIso8601String().split('T').first;
}
DateTime convertTimestampToDate(int timestamp) =>
DateTime.fromMillisecondsSinceEpoch((timestamp ?? 0) * 1000);
String convertTimestampToDateString(int timestamp) =>
convertTimestampToDate(timestamp).toIso8601String();
String formatDuration(Duration duration, {bool showSeconds = true}) {
final time = duration.toString().split('.')[0];
if (showSeconds) {
return time;
} else {
final parts = time.split(':');
return '${parts[0]}:${parts[1]}';
}
}
DateTime convertTimeOfDayToDateTime(TimeOfDay timeOfDay, DateTime dateTime) {
dateTime ??= DateTime.now();
return DateTime(dateTime.year, dateTime.month, dateTime.day,
timeOfDay?.hour ?? 0, timeOfDay?.minute ?? 0)
.toUtc();
}
TimeOfDay convertDateTimeToTimeOfDay(DateTime dateTime) =>
TimeOfDay(hour: dateTime?.hour ?? 0, minute: dateTime?.minute ?? 0);
String formatDateRange(String startDate, String endDate, BuildContext context) {
final today = DateTime.now();
final startDateTime = DateTime.tryParse(startDate).toLocal();
final startFormatter =
DateFormat(today.year == startDateTime.year ? 'MMM d' : 'MMM d, yyy');
final startDateTimeString = startFormatter.format(startDateTime);
final endDateTime = DateTime.tryParse(endDate).toLocal();
final endFormatter =
DateFormat(today.year == endDateTime.year ? 'MMM d' : 'MMM d, yyy');
final endDateTimeString = endFormatter.format(endDateTime);
return '$startDateTimeString - $endDateTimeString';
}
String formatDate(String value, BuildContext context,
{bool showDate = true, bool showTime = false, bool showSeconds = true}) {
if (value == null || value.isEmpty) {
return '';
}
final state = StoreProvider.of<AppState>(context).state;
final CompanyEntity company = state.company;
if (state.staticState.dateFormatMap.isEmpty) {
return '';
}
if (showTime) {
String format;
if (!showDate) {
format = showSeconds
? company.settings.enableMilitaryTime
? 'H:mm:ss'
: 'h:mm:ss a'
: company.settings.enableMilitaryTime
? 'H:mm'
: 'h:mm a';
} else {
final dateFormats = state.staticState.dateFormatMap;
final dateFormatId = (company.settings.dateFormatId ?? '').isNotEmpty
? company.settings.dateFormatId
: kDefaultDateFormat;
format = dateFormats[dateFormatId].format;
format += ' ' +
(showSeconds
? company.settings.enableMilitaryTime
? 'H:mm:ss'
: 'h:mm:ss a'
: company.settings.enableMilitaryTime
? 'H:mm'
: 'h:mm a');
}
final formatter = DateFormat(format, localeSelector(state));
final parsed = DateTime.tryParse(value);
return parsed == null ? '' : formatter.format(parsed.toLocal());
} else {
final dateFormats = state.staticState.dateFormatMap;
final formatter = DateFormat(
dateFormats[company.settings.dateFormatId].format,
localeSelector(state));
final parsed = DateTime.tryParse(value);
return parsed == null ? '' : formatter.format(parsed);
}
}
String formatApiUrl(String url) => cleanApiUrl(url) + '/api/v1';
String cleanApiUrl(String url) => (url ?? '')
.trim()
.replaceFirst(RegExp(r'/api/v1'), '')
.replaceFirst(RegExp(r'/$'), '');
String formatCustomValue({String value, String field, BuildContext context}) {
final localization = AppLocalization.of(context);
final state = StoreProvider.of<AppState>(context).state;
final CompanyEntity company = state.company;
switch (company.getCustomFieldType(field)) {
case kFieldTypeSwitch:
return value == 'yes' ? localization.yes : localization.no;
break;
case kFieldTypeDate:
return formatDate(value, context);
break;
default:
return value;
}
}