flutter_flow_util.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  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. Theme wrapInMaterialDatePickerTheme(
  49. BuildContext context,
  50. Widget child, {
  51. required Color headerBackgroundColor,
  52. required Color headerForegroundColor,
  53. required TextStyle headerTextStyle,
  54. required Color pickerBackgroundColor,
  55. required Color pickerForegroundColor,
  56. required Color selectedDateTimeBackgroundColor,
  57. required Color selectedDateTimeForegroundColor,
  58. required Color actionButtonForegroundColor,
  59. required double iconSize,
  60. }) {
  61. final baseTheme = Theme.of(context);
  62. final dateTimeMaterialStateForegroundColor =
  63. WidgetStateProperty.resolveWith((states) {
  64. if (states.contains(WidgetState.disabled)) {
  65. return pickerForegroundColor.applyAlpha(0.60);
  66. }
  67. if (states.contains(WidgetState.selected)) {
  68. return selectedDateTimeForegroundColor;
  69. }
  70. if (states.isEmpty) {
  71. return pickerForegroundColor;
  72. }
  73. return null;
  74. });
  75. final dateTimeMaterialStateBackgroundColor =
  76. WidgetStateProperty.resolveWith((states) {
  77. if (states.contains(WidgetState.selected)) {
  78. return selectedDateTimeBackgroundColor;
  79. }
  80. return null;
  81. });
  82. return Theme(
  83. data: baseTheme.copyWith(
  84. colorScheme: baseTheme.colorScheme.copyWith(
  85. onSurface: pickerForegroundColor,
  86. ),
  87. disabledColor: pickerForegroundColor.applyAlpha(0.3),
  88. textTheme: baseTheme.textTheme.copyWith(
  89. headlineSmall: headerTextStyle,
  90. headlineMedium: headerTextStyle,
  91. ),
  92. iconTheme: baseTheme.iconTheme.copyWith(
  93. size: iconSize,
  94. ),
  95. textButtonTheme: TextButtonThemeData(
  96. style: ButtonStyle(
  97. foregroundColor: WidgetStatePropertyAll(
  98. actionButtonForegroundColor,
  99. ),
  100. overlayColor: WidgetStateProperty.resolveWith((states) {
  101. if (states.contains(WidgetState.hovered)) {
  102. return actionButtonForegroundColor.applyAlpha(0.04);
  103. }
  104. if (states.contains(WidgetState.focused) ||
  105. states.contains(WidgetState.pressed)) {
  106. return actionButtonForegroundColor.applyAlpha(0.12);
  107. }
  108. return null;
  109. })),
  110. ),
  111. datePickerTheme: DatePickerThemeData(
  112. backgroundColor: pickerBackgroundColor,
  113. headerBackgroundColor: headerBackgroundColor,
  114. headerForegroundColor: headerForegroundColor,
  115. weekdayStyle: baseTheme.textTheme.labelMedium!.copyWith(
  116. color: pickerForegroundColor,
  117. ),
  118. dayBackgroundColor: dateTimeMaterialStateBackgroundColor,
  119. todayBackgroundColor: dateTimeMaterialStateBackgroundColor,
  120. yearBackgroundColor: dateTimeMaterialStateBackgroundColor,
  121. dayForegroundColor: dateTimeMaterialStateForegroundColor,
  122. todayForegroundColor: dateTimeMaterialStateForegroundColor,
  123. yearForegroundColor: dateTimeMaterialStateForegroundColor,
  124. ),
  125. ),
  126. child: child,
  127. );
  128. }
  129. Theme wrapInMaterialTimePickerTheme(
  130. BuildContext context,
  131. Widget child, {
  132. required Color headerBackgroundColor,
  133. required Color headerForegroundColor,
  134. required TextStyle headerTextStyle,
  135. required Color pickerBackgroundColor,
  136. required Color pickerForegroundColor,
  137. required Color selectedDateTimeBackgroundColor,
  138. required Color selectedDateTimeForegroundColor,
  139. required Color actionButtonForegroundColor,
  140. required double iconSize,
  141. }) {
  142. final baseTheme = Theme.of(context);
  143. return Theme(
  144. data: baseTheme.copyWith(
  145. iconTheme: baseTheme.iconTheme.copyWith(
  146. size: iconSize,
  147. ),
  148. textButtonTheme: TextButtonThemeData(
  149. style: ButtonStyle(
  150. foregroundColor: WidgetStatePropertyAll(
  151. actionButtonForegroundColor,
  152. ),
  153. overlayColor: WidgetStateProperty.resolveWith((states) {
  154. if (states.contains(WidgetState.hovered)) {
  155. return actionButtonForegroundColor.applyAlpha(0.04);
  156. }
  157. if (states.contains(WidgetState.focused) ||
  158. states.contains(WidgetState.pressed)) {
  159. return actionButtonForegroundColor.applyAlpha(0.12);
  160. }
  161. return null;
  162. })),
  163. ),
  164. timePickerTheme: baseTheme.timePickerTheme.copyWith(
  165. backgroundColor: pickerBackgroundColor,
  166. hourMinuteTextColor: pickerForegroundColor,
  167. dialHandColor: selectedDateTimeBackgroundColor,
  168. dialTextColor: WidgetStateColor.resolveWith((states) =>
  169. states.contains(WidgetState.selected)
  170. ? selectedDateTimeForegroundColor
  171. : pickerForegroundColor),
  172. dayPeriodBorderSide: BorderSide(
  173. color: pickerForegroundColor,
  174. ),
  175. dayPeriodTextColor: WidgetStateColor.resolveWith((states) =>
  176. states.contains(WidgetState.selected)
  177. ? selectedDateTimeForegroundColor
  178. : pickerForegroundColor),
  179. dayPeriodColor: WidgetStateColor.resolveWith((states) =>
  180. states.contains(WidgetState.selected)
  181. ? selectedDateTimeBackgroundColor
  182. : Colors.transparent),
  183. entryModeIconColor: pickerForegroundColor,
  184. ),
  185. ),
  186. child: child,
  187. );
  188. }
  189. Future launchURL(String url) async {
  190. var uri = Uri.parse(url);
  191. try {
  192. await launchUrl(uri);
  193. } catch (e) {
  194. throw 'Could not launch $uri: $e';
  195. }
  196. }
  197. Color colorFromCssString(String color, {Color? defaultColor}) {
  198. try {
  199. return fromCssColor(color);
  200. } catch (_) {}
  201. return defaultColor ?? Colors.black;
  202. }
  203. enum FormatType {
  204. decimal,
  205. percent,
  206. scientific,
  207. compact,
  208. compactLong,
  209. custom,
  210. }
  211. enum DecimalType {
  212. automatic,
  213. periodDecimal,
  214. commaDecimal,
  215. }
  216. String formatNumber(
  217. num? value, {
  218. required FormatType formatType,
  219. DecimalType? decimalType,
  220. String? currency,
  221. bool toLowerCase = false,
  222. String? format,
  223. String? locale,
  224. }) {
  225. if (value == null) {
  226. return '';
  227. }
  228. var formattedValue = '';
  229. switch (formatType) {
  230. case FormatType.decimal:
  231. switch (decimalType!) {
  232. case DecimalType.automatic:
  233. formattedValue = NumberFormat.decimalPattern().format(value);
  234. break;
  235. case DecimalType.periodDecimal:
  236. if (currency != null) {
  237. formattedValue = NumberFormat('#,##0.00', 'en_US').format(value);
  238. } else {
  239. formattedValue = NumberFormat.decimalPattern('en_US').format(value);
  240. }
  241. break;
  242. case DecimalType.commaDecimal:
  243. if (currency != null) {
  244. formattedValue = NumberFormat('#,##0.00', 'es_PA').format(value);
  245. } else {
  246. formattedValue = NumberFormat.decimalPattern('es_PA').format(value);
  247. }
  248. break;
  249. }
  250. break;
  251. case FormatType.percent:
  252. formattedValue = NumberFormat.percentPattern().format(value);
  253. break;
  254. case FormatType.scientific:
  255. formattedValue = NumberFormat.scientificPattern().format(value);
  256. if (toLowerCase) {
  257. formattedValue = formattedValue.toLowerCase();
  258. }
  259. break;
  260. case FormatType.compact:
  261. formattedValue = NumberFormat.compact().format(value);
  262. break;
  263. case FormatType.compactLong:
  264. formattedValue = NumberFormat.compactLong().format(value);
  265. break;
  266. case FormatType.custom:
  267. final hasLocale = locale != null && locale.isNotEmpty;
  268. formattedValue =
  269. NumberFormat(format, hasLocale ? locale : null).format(value);
  270. }
  271. if (formattedValue.isEmpty) {
  272. return value.toString();
  273. }
  274. if (currency != null) {
  275. final currencySymbol = currency.isNotEmpty
  276. ? currency
  277. : NumberFormat.simpleCurrency().format(0.0).substring(0, 1);
  278. formattedValue = '$currencySymbol$formattedValue';
  279. }
  280. return formattedValue;
  281. }
  282. DateTime get getCurrentTimestamp => DateTime.now();
  283. DateTime dateTimeFromSecondsSinceEpoch(int seconds) {
  284. return DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
  285. }
  286. extension DateTimeConversionExtension on DateTime {
  287. int get secondsSinceEpoch => (millisecondsSinceEpoch / 1000).round();
  288. }
  289. extension DateTimeComparisonOperators on DateTime {
  290. bool operator <(DateTime other) => isBefore(other);
  291. bool operator >(DateTime other) => isAfter(other);
  292. bool operator <=(DateTime other) => this < other || isAtSameMomentAs(other);
  293. bool operator >=(DateTime other) => this > other || isAtSameMomentAs(other);
  294. }
  295. T? castToType<T>(dynamic value) {
  296. if (value == null) {
  297. return null;
  298. }
  299. switch (T) {
  300. case double:
  301. // Doubles may be stored as ints in some cases.
  302. return value.toDouble() as T;
  303. case int:
  304. // Likewise, ints may be stored as doubles. If this is the case
  305. // (i.e. no decimal value), return the value as an int.
  306. if (value is num && value.toInt() == value) {
  307. return value.toInt() as T;
  308. }
  309. break;
  310. default:
  311. break;
  312. }
  313. return value as T;
  314. }
  315. dynamic getJsonField(
  316. dynamic response,
  317. String jsonPath, [
  318. bool isForList = false,
  319. ]) {
  320. final field = JsonPath(jsonPath).read(response);
  321. if (field.isEmpty) {
  322. return null;
  323. }
  324. if (field.length > 1) {
  325. return field.map((f) => f.value).toList();
  326. }
  327. final value = field.first.value;
  328. if (isForList) {
  329. return value is! Iterable
  330. ? [value]
  331. : (value is List ? value : value.toList());
  332. }
  333. return value;
  334. }
  335. Rect? getWidgetBoundingBox(BuildContext context) {
  336. try {
  337. final renderBox = context.findRenderObject() as RenderBox?;
  338. return renderBox!.localToGlobal(Offset.zero) & renderBox.size;
  339. } catch (_) {
  340. return null;
  341. }
  342. }
  343. bool get isAndroid => !kIsWeb && Platform.isAndroid;
  344. bool get isiOS => !kIsWeb && Platform.isIOS;
  345. bool get isWeb => kIsWeb;
  346. const kBreakpointSmall = 479.0;
  347. const kBreakpointMedium = 767.0;
  348. const kBreakpointLarge = 991.0;
  349. bool isMobileWidth(BuildContext context) =>
  350. MediaQuery.sizeOf(context).width < kBreakpointSmall;
  351. bool responsiveVisibility({
  352. required BuildContext context,
  353. bool phone = true,
  354. bool tablet = true,
  355. bool tabletLandscape = true,
  356. bool desktop = true,
  357. }) {
  358. final width = MediaQuery.sizeOf(context).width;
  359. if (width < kBreakpointSmall) {
  360. return phone;
  361. } else if (width < kBreakpointMedium) {
  362. return tablet;
  363. } else if (width < kBreakpointLarge) {
  364. return tabletLandscape;
  365. } else {
  366. return desktop;
  367. }
  368. }
  369. const kTextValidatorUsernameRegex = r'^[a-zA-Z][a-zA-Z0-9_-]{2,16}$';
  370. // https://stackoverflow.com/a/201378
  371. const kTextValidatorEmailRegex =
  372. "^(?:[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])+)\\])\$";
  373. const kTextValidatorWebsiteRegex =
  374. 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@:%_\+.~#?&//=]*)';
  375. extension FFTextEditingControllerExt on TextEditingController? {
  376. String get text => this == null ? '' : this!.text;
  377. set text(String newText) => this?.text = newText;
  378. }
  379. extension IterableExt<T> on Iterable<T> {
  380. List<T> sortedList<S extends Comparable>(
  381. {S Function(T)? keyOf, bool desc = false}) {
  382. final sortedAscending = toList()
  383. ..sort(keyOf == null ? null : ((a, b) => keyOf(a).compareTo(keyOf(b))));
  384. if (desc) {
  385. return sortedAscending.reversed.toList();
  386. }
  387. return sortedAscending;
  388. }
  389. List<S> mapIndexed<S>(S Function(int, T) func) => toList()
  390. .asMap()
  391. .map((index, value) => MapEntry(index, func(index, value)))
  392. .values
  393. .toList();
  394. }
  395. void setAppLanguage(BuildContext context, String language) =>
  396. MyApp.of(context).setLocale(language);
  397. void setDarkModeSetting(BuildContext context, ThemeMode themeMode) =>
  398. MyApp.of(context).setThemeMode(themeMode);
  399. void showSnackbar(
  400. BuildContext context,
  401. String message, {
  402. bool loading = false,
  403. int duration = 4,
  404. }) {
  405. ScaffoldMessenger.of(context).hideCurrentSnackBar();
  406. ScaffoldMessenger.of(context).showSnackBar(
  407. SnackBar(
  408. content: Row(
  409. children: [
  410. if (loading)
  411. Padding(
  412. padding: EdgeInsetsDirectional.only(end: 10.0),
  413. child: Container(
  414. height: 20,
  415. width: 20,
  416. child: const CircularProgressIndicator(
  417. color: Colors.white,
  418. ),
  419. ),
  420. ),
  421. Text(message),
  422. ],
  423. ),
  424. duration: Duration(seconds: duration),
  425. ),
  426. );
  427. }
  428. extension FFStringExt on String {
  429. String maybeHandleOverflow({int? maxChars, String replacement = ''}) =>
  430. maxChars != null && length > maxChars
  431. ? replaceRange(maxChars, null, replacement)
  432. : this;
  433. String toCapitalization(TextCapitalization textCapitalization) {
  434. switch (textCapitalization) {
  435. case TextCapitalization.none:
  436. return this;
  437. case TextCapitalization.words:
  438. return split(' ').map(toBeginningOfSentenceCase).join(' ');
  439. case TextCapitalization.sentences:
  440. return toBeginningOfSentenceCase(this);
  441. case TextCapitalization.characters:
  442. return toUpperCase();
  443. }
  444. }
  445. }
  446. extension ListFilterExt<T> on Iterable<T?> {
  447. List<T> get withoutNulls => where((s) => s != null).map((e) => e!).toList();
  448. }
  449. extension MapFilterExtensions<T> on Map<String, T?> {
  450. Map<String, T> get withoutNulls => Map.fromEntries(
  451. entries
  452. .where((e) => e.value != null)
  453. .map((e) => MapEntry(e.key, e.value as T)),
  454. );
  455. }
  456. extension MapListContainsExt on List<dynamic> {
  457. bool containsMap(dynamic map) => map is Map
  458. ? any((e) => e is Map && const DeepCollectionEquality().equals(e, map))
  459. : contains(map);
  460. }
  461. extension ListDivideExt<T extends Widget> on Iterable<T> {
  462. Iterable<MapEntry<int, Widget>> get enumerate => toList().asMap().entries;
  463. List<Widget> divide(Widget t, {bool Function(int)? filterFn}) => isEmpty
  464. ? []
  465. : (enumerate
  466. .map((e) => [e.value, if (filterFn == null || filterFn(e.key)) t])
  467. .expand((i) => i)
  468. .toList()
  469. ..removeLast());
  470. List<Widget> around(Widget t) => addToStart(t).addToEnd(t);
  471. List<Widget> addToStart(Widget t) =>
  472. enumerate.map((e) => e.value).toList()..insert(0, t);
  473. List<Widget> addToEnd(Widget t) =>
  474. enumerate.map((e) => e.value).toList()..add(t);
  475. List<Padding> paddingTopEach(double val) =>
  476. map((w) => Padding(padding: EdgeInsets.only(top: val), child: w))
  477. .toList();
  478. }
  479. extension StatefulWidgetExtensions on State<StatefulWidget> {
  480. /// Check if the widget exist before safely setting state.
  481. void safeSetState(VoidCallback fn) {
  482. if (mounted) {
  483. // ignore: invalid_use_of_protected_member
  484. setState(fn);
  485. }
  486. }
  487. }
  488. // For iOS 16 and below, set the status bar color to match the app's theme.
  489. // https://github.com/flutter/flutter/issues/41067
  490. Brightness? _lastBrightness;
  491. void fixStatusBarOniOS16AndBelow(BuildContext context) {
  492. if (!isiOS) {
  493. return;
  494. }
  495. final brightness = Theme.of(context).brightness;
  496. if (_lastBrightness != brightness) {
  497. _lastBrightness = brightness;
  498. SystemChrome.setSystemUIOverlayStyle(
  499. SystemUiOverlayStyle(
  500. statusBarBrightness: brightness,
  501. systemStatusBarContrastEnforced: true,
  502. ),
  503. );
  504. }
  505. }
  506. extension ColorOpacityExt on Color {
  507. Color applyAlpha(double val) => withValues(alpha: val);
  508. }
  509. String roundTo(double value, int decimalPoints) {
  510. final power = pow(10, decimalPoints);
  511. return ((value * power).round() / power).toString();
  512. }
  513. double computeGradientAlignmentX(double evaluatedAngle) {
  514. evaluatedAngle %= 360;
  515. final rads = evaluatedAngle * pi / 180;
  516. double x;
  517. if (evaluatedAngle < 45 || evaluatedAngle > 315) {
  518. x = sin(2 * rads);
  519. } else if (45 <= evaluatedAngle && evaluatedAngle <= 135) {
  520. x = 1;
  521. } else if (135 <= evaluatedAngle && evaluatedAngle <= 225) {
  522. x = sin(-2 * rads);
  523. } else {
  524. x = -1;
  525. }
  526. return double.parse(roundTo(x, 2));
  527. }
  528. double computeGradientAlignmentY(double evaluatedAngle) {
  529. evaluatedAngle %= 360;
  530. final rads = evaluatedAngle * pi / 180;
  531. double y;
  532. if (evaluatedAngle < 45 || evaluatedAngle > 315) {
  533. y = -1;
  534. } else if (45 <= evaluatedAngle && evaluatedAngle <= 135) {
  535. y = sin(-2 * rads);
  536. } else if (135 <= evaluatedAngle && evaluatedAngle <= 225) {
  537. y = 1;
  538. } else {
  539. y = sin(2 * rads);
  540. }
  541. return double.parse(roundTo(y, 2));
  542. }
  543. extension ListUniqueExt<T> on Iterable<T> {
  544. List<T> unique(dynamic Function(T) getKey) {
  545. var distinctSet = <dynamic>{};
  546. var distinctList = <T>[];
  547. for (var item in this) {
  548. if (distinctSet.add(getKey(item))) {
  549. distinctList.add(item);
  550. }
  551. }
  552. return distinctList;
  553. }
  554. }
  555. String getCurrentRoute(BuildContext context) =>
  556. context.mounted ? MyApp.of(context).getRoute() : '';
  557. List<String> getCurrentRouteStack(BuildContext context) =>
  558. context.mounted ? MyApp.of(context).getRouteStack() : [];