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(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(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(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; } }