flutter_flow_util.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. import 'dart:io';
  2. import 'package:flutter/foundation.dart' show kIsWeb;
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/services.dart';
  5. import 'package:collection/collection.dart';
  6. import 'package:from_css_color/from_css_color.dart';
  7. import 'dart:math' show pow, pi, sin;
  8. import 'package:intl/intl.dart';
  9. import 'package:json_path/json_path.dart';
  10. import 'package:timeago/timeago.dart' as timeago;
  11. import 'package:url_launcher/url_launcher.dart';
  12. import 'debug_util.dart';
  13. export 'debug_util.dart';
  14. export 'package:debug_panel_proto/debug_panel_proto.dart';
  15. export 'nav/serialization_util.dart';
  16. import '../main.dart';
  17. import 'lat_lng.dart';
  18. export 'keep_alive_wrapper.dart';
  19. export 'lat_lng.dart';
  20. export 'place.dart';
  21. export 'uploaded_file.dart';
  22. export '../app_state.dart';
  23. export 'flutter_flow_model.dart';
  24. export 'dart:math' show min, max;
  25. export 'dart:typed_data' show Uint8List;
  26. export 'dart:convert' show jsonEncode, jsonDecode;
  27. export 'package:intl/intl.dart';
  28. export 'package:page_transition/page_transition.dart';
  29. export 'internationalization.dart' show FFLocalizations;
  30. export 'nav/nav.dart';
  31. final RouteObserver<ModalRoute> routeObserver = RouteObserver<ModalRoute>();
  32. T valueOrDefault<T>(T? value, T defaultValue) =>
  33. (value is String && value.isEmpty) || value == null ? defaultValue : value;
  34. void _setTimeagoLocales() {
  35. timeago.setLocaleMessages('en', timeago.EnMessages());
  36. timeago.setLocaleMessages('en_short', timeago.EnShortMessages());
  37. }
  38. String dateTimeFormat(String format, DateTime? dateTime, {String? locale}) {
  39. if (dateTime == null) {
  40. return '';
  41. }
  42. if (format == 'relative') {
  43. _setTimeagoLocales();
  44. return timeago.format(dateTime, locale: locale, allowFromNow: true);
  45. }
  46. return DateFormat(format, locale).format(dateTime);
  47. }
  48. Future launchURL(String url) async {
  49. var uri = Uri.parse(url);
  50. try {
  51. await launchUrl(uri);
  52. } catch (e) {
  53. throw 'Could not launch $uri: $e';
  54. }
  55. }
  56. Color colorFromCssString(String color, {Color? defaultColor}) {
  57. try {
  58. return fromCssColor(color);
  59. } catch (_) {}
  60. return defaultColor ?? Colors.black;
  61. }
  62. enum FormatType {
  63. decimal,
  64. percent,
  65. scientific,
  66. compact,
  67. compactLong,
  68. custom,
  69. }
  70. enum DecimalType {
  71. automatic,
  72. periodDecimal,
  73. commaDecimal,
  74. }
  75. String formatNumber(
  76. num? value, {
  77. required FormatType formatType,
  78. DecimalType? decimalType,
  79. String? currency,
  80. bool toLowerCase = false,
  81. String? format,
  82. String? locale,
  83. }) {
  84. if (value == null) {
  85. return '';
  86. }
  87. var formattedValue = '';
  88. switch (formatType) {
  89. case FormatType.decimal:
  90. switch (decimalType!) {
  91. case DecimalType.automatic:
  92. formattedValue = NumberFormat.decimalPattern().format(value);
  93. break;
  94. case DecimalType.periodDecimal:
  95. if (currency != null) {
  96. formattedValue = NumberFormat('#,##0.00', 'en_US').format(value);
  97. } else {
  98. formattedValue = NumberFormat.decimalPattern('en_US').format(value);
  99. }
  100. break;
  101. case DecimalType.commaDecimal:
  102. if (currency != null) {
  103. formattedValue = NumberFormat('#,##0.00', 'es_PA').format(value);
  104. } else {
  105. formattedValue = NumberFormat.decimalPattern('es_PA').format(value);
  106. }
  107. break;
  108. }
  109. break;
  110. case FormatType.percent:
  111. formattedValue = NumberFormat.percentPattern().format(value);
  112. break;
  113. case FormatType.scientific:
  114. formattedValue = NumberFormat.scientificPattern().format(value);
  115. if (toLowerCase) {
  116. formattedValue = formattedValue.toLowerCase();
  117. }
  118. break;
  119. case FormatType.compact:
  120. formattedValue = NumberFormat.compact().format(value);
  121. break;
  122. case FormatType.compactLong:
  123. formattedValue = NumberFormat.compactLong().format(value);
  124. break;
  125. case FormatType.custom:
  126. final hasLocale = locale != null && locale.isNotEmpty;
  127. formattedValue =
  128. NumberFormat(format, hasLocale ? locale : null).format(value);
  129. }
  130. if (formattedValue.isEmpty) {
  131. return value.toString();
  132. }
  133. if (currency != null) {
  134. final currencySymbol = currency.isNotEmpty
  135. ? currency
  136. : NumberFormat.simpleCurrency().format(0.0).substring(0, 1);
  137. formattedValue = '$currencySymbol$formattedValue';
  138. }
  139. return formattedValue;
  140. }
  141. DateTime get getCurrentTimestamp => DateTime.now();
  142. DateTime dateTimeFromSecondsSinceEpoch(int seconds) {
  143. return DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
  144. }
  145. extension DateTimeConversionExtension on DateTime {
  146. int get secondsSinceEpoch => (millisecondsSinceEpoch / 1000).round();
  147. }
  148. extension DateTimeComparisonOperators on DateTime {
  149. bool operator <(DateTime other) => isBefore(other);
  150. bool operator >(DateTime other) => isAfter(other);
  151. bool operator <=(DateTime other) => this < other || isAtSameMomentAs(other);
  152. bool operator >=(DateTime other) => this > other || isAtSameMomentAs(other);
  153. }
  154. T? castToType<T>(dynamic value) {
  155. if (value == null) {
  156. return null;
  157. }
  158. switch (T) {
  159. case double:
  160. // Doubles may be stored as ints in some cases.
  161. return value.toDouble() as T;
  162. case int:
  163. // Likewise, ints may be stored as doubles. If this is the case
  164. // (i.e. no decimal value), return the value as an int.
  165. if (value is num && value.toInt() == value) {
  166. return value.toInt() as T;
  167. }
  168. break;
  169. default:
  170. break;
  171. }
  172. return value as T;
  173. }
  174. dynamic getJsonField(
  175. dynamic response,
  176. String jsonPath, [
  177. bool isForList = false,
  178. ]) {
  179. final field = JsonPath(jsonPath).read(response);
  180. if (field.isEmpty) {
  181. return null;
  182. }
  183. if (field.length > 1) {
  184. return field.map((f) => f.value).toList();
  185. }
  186. final value = field.first.value;
  187. if (isForList) {
  188. return value is! Iterable
  189. ? [value]
  190. : (value is List ? value : value.toList());
  191. }
  192. return value;
  193. }
  194. Rect? getWidgetBoundingBox(BuildContext context) {
  195. try {
  196. final renderBox = context.findRenderObject() as RenderBox?;
  197. return renderBox!.localToGlobal(Offset.zero) & renderBox.size;
  198. } catch (_) {
  199. return null;
  200. }
  201. }
  202. bool get isAndroid => !kIsWeb && Platform.isAndroid;
  203. bool get isiOS => !kIsWeb && Platform.isIOS;
  204. bool get isWeb => kIsWeb;
  205. const kBreakpointSmall = 479.0;
  206. const kBreakpointMedium = 767.0;
  207. const kBreakpointLarge = 991.0;
  208. bool isMobileWidth(BuildContext context) =>
  209. MediaQuery.sizeOf(context).width < kBreakpointSmall;
  210. bool responsiveVisibility({
  211. required BuildContext context,
  212. bool phone = true,
  213. bool tablet = true,
  214. bool tabletLandscape = true,
  215. bool desktop = true,
  216. }) {
  217. final width = MediaQuery.sizeOf(context).width;
  218. if (width < kBreakpointSmall) {
  219. return phone;
  220. } else if (width < kBreakpointMedium) {
  221. return tablet;
  222. } else if (width < kBreakpointLarge) {
  223. return tabletLandscape;
  224. } else {
  225. return desktop;
  226. }
  227. }
  228. const kTextValidatorUsernameRegex = r'^[a-zA-Z][a-zA-Z0-9_-]{2,16}$';
  229. // https://stackoverflow.com/a/201378
  230. const kTextValidatorEmailRegex =
  231. "^(?:[a-zA-Z0-9!#\$%&\'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#\$%&\'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-zA-Z0-9-]*[a-zA-Z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])\$";
  232. const kTextValidatorWebsiteRegex =
  233. r'(https?:\/\/)?(www\.)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,10}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)|(https?:\/\/)?(www\.)?(?!ww)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,10}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)';
  234. extension FFTextEditingControllerExt on TextEditingController? {
  235. String get text => this == null ? '' : this!.text;
  236. set text(String newText) => this?.text = newText;
  237. }
  238. extension IterableExt<T> on Iterable<T> {
  239. List<T> sortedList<S extends Comparable>(
  240. {S Function(T)? keyOf, bool desc = false}) {
  241. final sortedAscending = toList()
  242. ..sort(keyOf == null ? null : ((a, b) => keyOf(a).compareTo(keyOf(b))));
  243. if (desc) {
  244. return sortedAscending.reversed.toList();
  245. }
  246. return sortedAscending;
  247. }
  248. List<S> mapIndexed<S>(S Function(int, T) func) => toList()
  249. .asMap()
  250. .map((index, value) => MapEntry(index, func(index, value)))
  251. .values
  252. .toList();
  253. }
  254. void setAppLanguage(BuildContext context, String language) =>
  255. MyApp.of(context).setLocale(language);
  256. void setDarkModeSetting(BuildContext context, ThemeMode themeMode) =>
  257. MyApp.of(context).setThemeMode(themeMode);
  258. void showSnackbar(
  259. BuildContext context,
  260. String message, {
  261. bool loading = false,
  262. int duration = 4,
  263. }) {
  264. ScaffoldMessenger.of(context).hideCurrentSnackBar();
  265. ScaffoldMessenger.of(context).showSnackBar(
  266. SnackBar(
  267. content: Row(
  268. children: [
  269. if (loading)
  270. Padding(
  271. padding: EdgeInsetsDirectional.only(end: 10.0),
  272. child: Container(
  273. height: 20,
  274. width: 20,
  275. child: const CircularProgressIndicator(
  276. color: Colors.white,
  277. ),
  278. ),
  279. ),
  280. Text(message),
  281. ],
  282. ),
  283. duration: Duration(seconds: duration),
  284. ),
  285. );
  286. }
  287. extension FFStringExt on String {
  288. String maybeHandleOverflow({int? maxChars, String replacement = ''}) =>
  289. maxChars != null && length > maxChars
  290. ? replaceRange(maxChars, null, replacement)
  291. : this;
  292. String toCapitalization(TextCapitalization textCapitalization) {
  293. switch (textCapitalization) {
  294. case TextCapitalization.none:
  295. return this;
  296. case TextCapitalization.words:
  297. return split(' ').map(toBeginningOfSentenceCase).join(' ');
  298. case TextCapitalization.sentences:
  299. return toBeginningOfSentenceCase(this);
  300. case TextCapitalization.characters:
  301. return toUpperCase();
  302. }
  303. }
  304. }
  305. extension ListFilterExt<T> on Iterable<T?> {
  306. List<T> get withoutNulls => where((s) => s != null).map((e) => e!).toList();
  307. }
  308. extension MapFilterExtensions<T> on Map<String, T?> {
  309. Map<String, T> get withoutNulls => Map.fromEntries(
  310. entries
  311. .where((e) => e.value != null)
  312. .map((e) => MapEntry(e.key, e.value as T)),
  313. );
  314. }
  315. extension MapListContainsExt on List<dynamic> {
  316. bool containsMap(dynamic map) => map is Map
  317. ? any((e) => e is Map && const DeepCollectionEquality().equals(e, map))
  318. : contains(map);
  319. }
  320. extension ListDivideExt<T extends Widget> on Iterable<T> {
  321. Iterable<MapEntry<int, Widget>> get enumerate => toList().asMap().entries;
  322. List<Widget> divide(Widget t, {bool Function(int)? filterFn}) => isEmpty
  323. ? []
  324. : (enumerate
  325. .map((e) => [e.value, if (filterFn == null || filterFn(e.key)) t])
  326. .expand((i) => i)
  327. .toList()
  328. ..removeLast());
  329. List<Widget> around(Widget t) => addToStart(t).addToEnd(t);
  330. List<Widget> addToStart(Widget t) =>
  331. enumerate.map((e) => e.value).toList()..insert(0, t);
  332. List<Widget> addToEnd(Widget t) =>
  333. enumerate.map((e) => e.value).toList()..add(t);
  334. List<Padding> paddingTopEach(double val) =>
  335. map((w) => Padding(padding: EdgeInsets.only(top: val), child: w))
  336. .toList();
  337. }
  338. extension StatefulWidgetExtensions on State<StatefulWidget> {
  339. /// Check if the widget exist before safely setting state.
  340. void safeSetState(VoidCallback fn) {
  341. if (mounted) {
  342. // ignore: invalid_use_of_protected_member
  343. setState(fn);
  344. }
  345. }
  346. }
  347. // For iOS 16 and below, set the status bar color to match the app's theme.
  348. // https://github.com/flutter/flutter/issues/41067
  349. Brightness? _lastBrightness;
  350. void fixStatusBarOniOS16AndBelow(BuildContext context) {
  351. if (!isiOS) {
  352. return;
  353. }
  354. final brightness = Theme.of(context).brightness;
  355. if (_lastBrightness != brightness) {
  356. _lastBrightness = brightness;
  357. SystemChrome.setSystemUIOverlayStyle(
  358. SystemUiOverlayStyle(
  359. statusBarBrightness: brightness,
  360. systemStatusBarContrastEnforced: true,
  361. ),
  362. );
  363. }
  364. }
  365. extension ColorOpacityExt on Color {
  366. Color applyAlpha(double val) => withValues(alpha: val);
  367. }
  368. String roundTo(double value, int decimalPoints) {
  369. final power = pow(10, decimalPoints);
  370. return ((value * power).round() / power).toString();
  371. }
  372. double computeGradientAlignmentX(double evaluatedAngle) {
  373. evaluatedAngle %= 360;
  374. final rads = evaluatedAngle * pi / 180;
  375. double x;
  376. if (evaluatedAngle < 45 || evaluatedAngle > 315) {
  377. x = sin(2 * rads);
  378. } else if (45 <= evaluatedAngle && evaluatedAngle <= 135) {
  379. x = 1;
  380. } else if (135 <= evaluatedAngle && evaluatedAngle <= 225) {
  381. x = sin(-2 * rads);
  382. } else {
  383. x = -1;
  384. }
  385. return double.parse(roundTo(x, 2));
  386. }
  387. double computeGradientAlignmentY(double evaluatedAngle) {
  388. evaluatedAngle %= 360;
  389. final rads = evaluatedAngle * pi / 180;
  390. double y;
  391. if (evaluatedAngle < 45 || evaluatedAngle > 315) {
  392. y = -1;
  393. } else if (45 <= evaluatedAngle && evaluatedAngle <= 135) {
  394. y = sin(-2 * rads);
  395. } else if (135 <= evaluatedAngle && evaluatedAngle <= 225) {
  396. y = 1;
  397. } else {
  398. y = sin(2 * rads);
  399. }
  400. return double.parse(roundTo(y, 2));
  401. }
  402. extension ListUniqueExt<T> on Iterable<T> {
  403. List<T> unique(dynamic Function(T) getKey) {
  404. var distinctSet = <dynamic>{};
  405. var distinctList = <T>[];
  406. for (var item in this) {
  407. if (distinctSet.add(getKey(item))) {
  408. distinctList.add(item);
  409. }
  410. }
  411. return distinctList;
  412. }
  413. }
  414. String getCurrentRoute(BuildContext context) =>
  415. context.mounted ? MyApp.of(context).getRoute() : '';
  416. List<String> getCurrentRouteStack(BuildContext context) =>
  417. context.mounted ? MyApp.of(context).getRouteStack() : [];