797 lines
25 KiB
Dart
797 lines
25 KiB
Dart
import 'dart:io';
|
|
import 'dart:math' show pow, pi, sin;
|
|
|
|
import 'package:collection/collection.dart';
|
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:from_css_color/from_css_color.dart';
|
|
import 'package:hub/flutter_flow/flutter_flow_theme.dart';
|
|
import 'package:hub/flutter_flow/flutter_flow_widgets.dart';
|
|
import 'package:hub/flutter_flow/internationalization.dart';
|
|
import 'package:hub/flutter_flow/nav/nav.dart';
|
|
import 'package:hub/shared/utils/limited_text_size.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:json_path/json_path.dart';
|
|
import 'package:timeago/timeago.dart' as timeago;
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
import '../main.dart';
|
|
|
|
export 'dart:convert' show jsonEncode, jsonDecode;
|
|
export 'dart:math' show min, max;
|
|
export 'dart:typed_data' show Uint8List;
|
|
|
|
export 'package:intl/intl.dart';
|
|
export 'package:page_transition/page_transition.dart';
|
|
|
|
export 'custom_icons.dart' show FFIcons;
|
|
export 'flutter_flow_model.dart';
|
|
export 'internationalization.dart' show FFLocalizations;
|
|
export 'keep_alive_wrapper.dart';
|
|
export 'lat_lng.dart';
|
|
export 'place.dart';
|
|
export 'uploaded_file.dart';
|
|
|
|
T valueOrDefault<T>(T? value, T defaultValue) =>
|
|
(value is String && value.isEmpty) || value == null ? defaultValue : value;
|
|
|
|
void _setTimeagoLocales() {
|
|
timeago.setLocaleMessages('pt', timeago.PtBrMessages());
|
|
timeago.setLocaleMessages('pt_short', timeago.PtBrShortMessages());
|
|
timeago.setLocaleMessages('en', timeago.EnMessages());
|
|
timeago.setLocaleMessages('en_short', timeago.EnShortMessages());
|
|
}
|
|
|
|
String dateTimeFormat(String format, DateTime? dateTime, {String? locale}) {
|
|
if (dateTime == null) {
|
|
return '';
|
|
}
|
|
if (format == 'relative') {
|
|
_setTimeagoLocales();
|
|
return timeago.format(dateTime, locale: locale, allowFromNow: true);
|
|
}
|
|
return DateFormat(format, locale).format(dateTime);
|
|
}
|
|
|
|
Theme wrapInMaterialDatePickerTheme(
|
|
BuildContext context,
|
|
Widget child, {
|
|
required Color headerBackgroundColor,
|
|
required Color headerForegroundColor,
|
|
required TextStyle headerTextStyle,
|
|
required Color pickerBackgroundColor,
|
|
required Color pickerForegroundColor,
|
|
required Color selectedDateTimeBackgroundColor,
|
|
required Color selectedDateTimeForegroundColor,
|
|
required Color actionButtonForegroundColor,
|
|
required double iconSize,
|
|
}) {
|
|
final baseTheme = Theme.of(context);
|
|
final dateTimeMaterialStateForegroundColor =
|
|
WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.disabled)) {
|
|
return pickerForegroundColor.withOpacity(0.60);
|
|
}
|
|
if (states.contains(WidgetState.selected)) {
|
|
return selectedDateTimeForegroundColor;
|
|
}
|
|
if (states.isEmpty) {
|
|
return pickerForegroundColor;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
final dateTimeMaterialStateBackgroundColor =
|
|
WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return selectedDateTimeBackgroundColor;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
return Theme(
|
|
data: baseTheme.copyWith(
|
|
textSelectionTheme: baseTheme.textSelectionTheme.copyWith(
|
|
cursorColor: headerBackgroundColor,
|
|
selectionColor: headerBackgroundColor.withOpacity(0.4),
|
|
selectionHandleColor: headerBackgroundColor,
|
|
),
|
|
inputDecorationTheme: baseTheme.inputDecorationTheme.copyWith(
|
|
isDense: true,
|
|
hoverColor: headerBackgroundColor.withOpacity(0.04),
|
|
floatingLabelStyle: baseTheme.textTheme.labelMedium!.copyWith(
|
|
color: headerBackgroundColor,
|
|
),
|
|
labelStyle: baseTheme.textTheme.labelMedium!.copyWith(
|
|
color: headerBackgroundColor,
|
|
),
|
|
border: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor,
|
|
),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor.withOpacity(0.6),
|
|
),
|
|
),
|
|
errorBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor,
|
|
),
|
|
),
|
|
focusedErrorBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor,
|
|
),
|
|
),
|
|
),
|
|
colorScheme: baseTheme.colorScheme.copyWith(
|
|
onSurface: pickerForegroundColor,
|
|
),
|
|
disabledColor: pickerForegroundColor.withOpacity(0.3),
|
|
textTheme: baseTheme.textTheme.copyWith(
|
|
headlineSmall: headerTextStyle,
|
|
headlineMedium: headerTextStyle,
|
|
),
|
|
iconTheme: baseTheme.iconTheme.copyWith(
|
|
size: iconSize,
|
|
),
|
|
textButtonTheme: TextButtonThemeData(
|
|
style: ButtonStyle(
|
|
foregroundColor: WidgetStatePropertyAll(
|
|
actionButtonForegroundColor,
|
|
),
|
|
overlayColor: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return actionButtonForegroundColor.withOpacity(0.04);
|
|
}
|
|
if (states.contains(WidgetState.focused) ||
|
|
states.contains(WidgetState.pressed)) {
|
|
return actionButtonForegroundColor.withOpacity(0.12);
|
|
}
|
|
return null;
|
|
})),
|
|
),
|
|
datePickerTheme: DatePickerThemeData(
|
|
backgroundColor: pickerBackgroundColor,
|
|
headerBackgroundColor: headerBackgroundColor,
|
|
headerForegroundColor: headerForegroundColor,
|
|
weekdayStyle: baseTheme.textTheme.labelMedium!.copyWith(
|
|
color: pickerForegroundColor,
|
|
),
|
|
dayBackgroundColor: dateTimeMaterialStateBackgroundColor,
|
|
todayBackgroundColor: dateTimeMaterialStateBackgroundColor,
|
|
yearBackgroundColor: dateTimeMaterialStateBackgroundColor,
|
|
dayForegroundColor: dateTimeMaterialStateForegroundColor,
|
|
todayForegroundColor: dateTimeMaterialStateForegroundColor,
|
|
yearForegroundColor: dateTimeMaterialStateForegroundColor,
|
|
),
|
|
),
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
Theme wrapInMaterialTimePickerTheme(
|
|
BuildContext context,
|
|
Widget child, {
|
|
required Color headerBackgroundColor,
|
|
required Color headerForegroundColor,
|
|
required TextStyle headerTextStyle,
|
|
required Color pickerBackgroundColor,
|
|
required Color pickerDialForegroundColor,
|
|
required Color pickerForegroundColor,
|
|
required Color selectedDateTimeBackgroundColor,
|
|
required Color selectedDateTimeForegroundColor,
|
|
required Color actionButtonForegroundColor,
|
|
required double iconSize,
|
|
}) {
|
|
final baseTheme = Theme.of(context);
|
|
|
|
final textScaler = MediaQuery.textScalerOf(context);
|
|
final double baseFontSize = 14.0;
|
|
final double scaledFontSize = baseFontSize * textScaler.scale(1);
|
|
final double limitedFontSize = scaledFontSize > 14.0 ? 8 : scaledFontSize;
|
|
|
|
return Theme(
|
|
data: baseTheme.copyWith(
|
|
focusColor: headerBackgroundColor,
|
|
iconTheme: baseTheme.iconTheme.copyWith(
|
|
size: iconSize,
|
|
),
|
|
textSelectionTheme: baseTheme.textSelectionTheme.copyWith(
|
|
cursorColor: headerBackgroundColor,
|
|
selectionColor: headerBackgroundColor.withOpacity(0.4),
|
|
selectionHandleColor: headerBackgroundColor,
|
|
),
|
|
inputDecorationTheme: baseTheme.inputDecorationTheme.copyWith(
|
|
isDense: true,
|
|
focusColor: headerBackgroundColor,
|
|
hoverColor: headerBackgroundColor.withOpacity(0.04),
|
|
floatingLabelStyle: baseTheme.textTheme.labelMedium!.copyWith(
|
|
color: headerBackgroundColor,
|
|
),
|
|
labelStyle: baseTheme.textTheme.labelMedium!.copyWith(
|
|
color: headerBackgroundColor,
|
|
),
|
|
outlineBorder: BorderSide(
|
|
color: headerBackgroundColor,
|
|
),
|
|
border: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor,
|
|
),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor.withOpacity(0.6),
|
|
),
|
|
),
|
|
errorBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor,
|
|
),
|
|
),
|
|
focusedErrorBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: headerBackgroundColor,
|
|
),
|
|
),
|
|
),
|
|
textButtonTheme: TextButtonThemeData(
|
|
style: ButtonStyle(
|
|
foregroundColor: WidgetStatePropertyAll(
|
|
actionButtonForegroundColor,
|
|
),
|
|
overlayColor: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return actionButtonForegroundColor.withOpacity(0.04);
|
|
}
|
|
if (states.contains(WidgetState.focused) ||
|
|
states.contains(WidgetState.pressed)) {
|
|
return actionButtonForegroundColor.withOpacity(0.12);
|
|
}
|
|
return null;
|
|
})),
|
|
),
|
|
timePickerTheme: baseTheme.timePickerTheme.copyWith(
|
|
hourMinuteColor: selectedDateTimeBackgroundColor,
|
|
backgroundColor: pickerBackgroundColor,
|
|
hourMinuteTextColor: pickerForegroundColor,
|
|
dialHandColor: selectedDateTimeBackgroundColor,
|
|
dialTextColor: WidgetStateColor.resolveWith((states) =>
|
|
states.contains(WidgetState.selected)
|
|
? selectedDateTimeForegroundColor
|
|
: pickerDialForegroundColor),
|
|
dayPeriodBorderSide: BorderSide(
|
|
color: pickerForegroundColor,
|
|
),
|
|
dialTextStyle: baseTheme.textTheme.headlineMedium!.copyWith(
|
|
color: pickerDialForegroundColor,
|
|
fontSize: limitedFontSize,
|
|
),
|
|
dayPeriodTextColor: WidgetStateColor.resolveWith((states) =>
|
|
states.contains(WidgetState.selected)
|
|
? selectedDateTimeForegroundColor
|
|
: pickerForegroundColor),
|
|
dayPeriodColor: WidgetStateColor.resolveWith((states) =>
|
|
states.contains(WidgetState.selected)
|
|
? selectedDateTimeBackgroundColor
|
|
: Colors.transparent),
|
|
entryModeIconColor: pickerForegroundColor,
|
|
),
|
|
),
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
Future launchURL(String url) async {
|
|
var uri = Uri.parse(url);
|
|
try {
|
|
await launchUrl(uri);
|
|
} catch (e) {
|
|
throw 'Could not launch $uri: $e';
|
|
}
|
|
}
|
|
|
|
Color colorFromCssString(String color, {Color? defaultColor}) {
|
|
try {
|
|
return fromCssColor(color);
|
|
} catch (_) {}
|
|
return defaultColor ?? Colors.black;
|
|
}
|
|
|
|
enum FormatType {
|
|
decimal,
|
|
percent,
|
|
scientific,
|
|
compact,
|
|
compactLong,
|
|
custom,
|
|
}
|
|
|
|
enum DecimalType {
|
|
automatic,
|
|
periodDecimal,
|
|
commaDecimal,
|
|
}
|
|
|
|
String formatNumber(
|
|
num? value, {
|
|
required FormatType formatType,
|
|
DecimalType? decimalType,
|
|
String? currency,
|
|
bool toLowerCase = false,
|
|
String? format,
|
|
String? locale,
|
|
}) {
|
|
if (value == null) {
|
|
return '';
|
|
}
|
|
var formattedValue = '';
|
|
switch (formatType) {
|
|
case FormatType.decimal:
|
|
switch (decimalType!) {
|
|
case DecimalType.automatic:
|
|
formattedValue = NumberFormat.decimalPattern().format(value);
|
|
break;
|
|
case DecimalType.periodDecimal:
|
|
formattedValue = NumberFormat.decimalPattern('en_US').format(value);
|
|
break;
|
|
case DecimalType.commaDecimal:
|
|
formattedValue = NumberFormat.decimalPattern('es_PA').format(value);
|
|
break;
|
|
}
|
|
break;
|
|
case FormatType.percent:
|
|
formattedValue = NumberFormat.percentPattern().format(value);
|
|
break;
|
|
case FormatType.scientific:
|
|
formattedValue = NumberFormat.scientificPattern().format(value);
|
|
if (toLowerCase) {
|
|
formattedValue = formattedValue.toLowerCase();
|
|
}
|
|
break;
|
|
case FormatType.compact:
|
|
formattedValue = NumberFormat.compact().format(value);
|
|
break;
|
|
case FormatType.compactLong:
|
|
formattedValue = NumberFormat.compactLong().format(value);
|
|
break;
|
|
case FormatType.custom:
|
|
final hasLocale = locale != null && locale.isNotEmpty;
|
|
formattedValue =
|
|
NumberFormat(format, hasLocale ? locale : null).format(value);
|
|
}
|
|
|
|
if (formattedValue.isEmpty) {
|
|
return value.toString();
|
|
}
|
|
|
|
if (currency != null) {
|
|
final currencySymbol = currency.isNotEmpty
|
|
? currency
|
|
: NumberFormat.simpleCurrency().format(0.0).substring(0, 1);
|
|
formattedValue = '$currencySymbol$formattedValue';
|
|
}
|
|
|
|
return formattedValue;
|
|
}
|
|
|
|
DateTime get getCurrentTimestamp => DateTime.now();
|
|
|
|
DateTime dateTimeFromSecondsSinceEpoch(int seconds) {
|
|
return DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
|
|
}
|
|
|
|
extension DateTimeConversionExtension on DateTime {
|
|
int get secondsSinceEpoch => (millisecondsSinceEpoch / 1000).round();
|
|
}
|
|
|
|
extension DateTimeComparisonOperators on DateTime {
|
|
bool operator <(DateTime other) => isBefore(other);
|
|
|
|
bool operator >(DateTime other) => isAfter(other);
|
|
|
|
bool operator <=(DateTime other) => this < other || isAtSameMomentAs(other);
|
|
|
|
bool operator >=(DateTime other) => this > other || isAtSameMomentAs(other);
|
|
}
|
|
|
|
T? castToType<T>(dynamic value) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
switch (T) {
|
|
case double:
|
|
// Doubles may be stored as ints in some cases.
|
|
return value.toDouble() as T;
|
|
case int:
|
|
// Likewise, ints may be stored as doubles. If this is the case
|
|
// (i.e. no decimal value), return the value as an int.
|
|
if (value is num && value.toInt() == value) {
|
|
return value.toInt() as T;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return value as T;
|
|
}
|
|
|
|
dynamic getJsonField(
|
|
dynamic response,
|
|
String jsonPath, [
|
|
bool isForList = false,
|
|
]) {
|
|
final field = JsonPath(jsonPath).read(response);
|
|
if (field.isEmpty) {
|
|
return null;
|
|
}
|
|
if (field.length > 1) {
|
|
return field.map((f) => f.value).toList();
|
|
}
|
|
final value = field.first.value;
|
|
if (isForList) {
|
|
return value is! Iterable
|
|
? [value]
|
|
: (value is List ? value : value.toList());
|
|
}
|
|
return value;
|
|
}
|
|
|
|
Rect? getWidgetBoundingBox(BuildContext context) {
|
|
try {
|
|
final renderBox = context.findRenderObject() as RenderBox?;
|
|
return renderBox!.localToGlobal(Offset.zero) & renderBox.size;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
bool get isAndroid => !kIsWeb && Platform.isAndroid;
|
|
|
|
bool get isiOS => !kIsWeb && Platform.isIOS;
|
|
|
|
bool get isWeb => kIsWeb;
|
|
|
|
const kBreakpointSmall = 479.0;
|
|
const kBreakpointMedium = 767.0;
|
|
const kBreakpointLarge = 991.0;
|
|
|
|
bool isMobileWidth(BuildContext context) =>
|
|
MediaQuery.sizeOf(context).width < kBreakpointSmall;
|
|
|
|
bool responsiveVisibility({
|
|
required BuildContext context,
|
|
bool phone = true,
|
|
bool tablet = true,
|
|
bool tabletLandscape = true,
|
|
bool desktop = true,
|
|
}) {
|
|
final width = MediaQuery.sizeOf(context).width;
|
|
if (width < kBreakpointSmall) {
|
|
return phone;
|
|
} else if (width < kBreakpointMedium) {
|
|
return tablet;
|
|
} else if (width < kBreakpointLarge) {
|
|
return tabletLandscape;
|
|
} else {
|
|
return desktop;
|
|
}
|
|
}
|
|
|
|
const kTextValidatorUsernameRegex = r'^[a-zA-Z][a-zA-Z0-9_-]{2,16}$';
|
|
// https://stackoverflow.com/a/201378
|
|
const kTextValidatorEmailRegex =
|
|
"^(?:[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])+)\\])\$";
|
|
const kTextValidatorWebsiteRegex =
|
|
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@:%_\+.~#?&//=]*)';
|
|
|
|
extension FFTextEditingControllerExt on TextEditingController? {
|
|
String get text => this == null ? '' : this!.text;
|
|
|
|
set text(String newText) => this?.text = newText;
|
|
}
|
|
|
|
extension IterableExt<T> on Iterable<T> {
|
|
List<T> sortedList<S extends Comparable>(
|
|
{S Function(T)? keyOf, bool desc = false}) {
|
|
final sortedAscending = toList()
|
|
..sort(keyOf == null ? null : ((a, b) => keyOf(a).compareTo(keyOf(b))));
|
|
if (desc) {
|
|
return sortedAscending.reversed.toList();
|
|
}
|
|
return sortedAscending;
|
|
}
|
|
|
|
List<S> mapIndexed<S>(S Function(int, T) func) => toList()
|
|
.asMap()
|
|
.map((index, value) => MapEntry(index, func(index, value)))
|
|
.values
|
|
.toList();
|
|
}
|
|
|
|
void setAppLanguage(BuildContext context, String language) =>
|
|
App.of(context).setLocale(language);
|
|
|
|
void setDarkModeSetting(BuildContext context, ThemeMode themeMode) =>
|
|
App.of(context).setThemeMode(themeMode);
|
|
|
|
void showSnackbarMessenger(
|
|
BuildContext context,
|
|
String message,
|
|
bool error, {
|
|
bool loading = false,
|
|
int duration = 4,
|
|
}) {
|
|
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(showSnackbar(context, message, error));
|
|
}
|
|
|
|
SnackBar showSnackbar(
|
|
BuildContext context,
|
|
String message,
|
|
bool error, {
|
|
bool loading = false,
|
|
int duration = 4,
|
|
}) {
|
|
return SnackBar(
|
|
content: Row(
|
|
children: [
|
|
if (loading)
|
|
Padding(
|
|
padding: const EdgeInsetsDirectional.only(end: 10.0),
|
|
child: SizedBox(
|
|
height: 20,
|
|
width: 20,
|
|
child: CircularProgressIndicator(
|
|
color: FlutterFlowTheme.of(context).info,
|
|
),
|
|
),
|
|
),
|
|
Text(
|
|
message,
|
|
style: TextStyle(
|
|
color: FlutterFlowTheme.of(context).info,
|
|
fontSize: LimitedFontSizeUtil.getBodyFontSize(context),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
duration: Duration(seconds: duration),
|
|
backgroundColor: error
|
|
? FlutterFlowTheme.of(context).error
|
|
: FlutterFlowTheme.of(context).success,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(30),
|
|
),
|
|
);
|
|
}
|
|
|
|
void showAlertDialog(BuildContext context, String title, String content,
|
|
Future<void> Function() action) {
|
|
double limitedBodyFontSize = LimitedFontSizeUtil.getBodyFontSize(context);
|
|
double limitedHeaderFontSize = LimitedFontSizeUtil.getHeaderFontSize(context);
|
|
double limitedInputFontSize = LimitedFontSizeUtil.getInputFontSize(context);
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
key: ValueKey<String>('AlertDialogKey'),
|
|
backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
|
|
title: Text(title,
|
|
style: TextStyle(
|
|
color: FlutterFlowTheme.of(context).primaryText,
|
|
fontSize: limitedHeaderFontSize),
|
|
textAlign: TextAlign.center),
|
|
content: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(content,
|
|
style: TextStyle(
|
|
color: FlutterFlowTheme.of(context).primaryText,
|
|
fontSize: limitedBodyFontSize)),
|
|
),
|
|
actions: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
mainAxisSize: MainAxisSize.max,
|
|
children: [
|
|
FFButtonWidget(
|
|
key: ValueKey<String>('RejectOptionKey'),
|
|
onPressed: () => context.pop(), //Navigator.pop(context),
|
|
options: FFButtonOptions(
|
|
width: MediaQuery.of(context).size.width * 0.3,
|
|
height: 50,
|
|
color: FlutterFlowTheme.of(context).primaryBackground,
|
|
textStyle: TextStyle(
|
|
color: FlutterFlowTheme.of(context).primaryText,
|
|
fontSize: limitedInputFontSize),
|
|
borderSide: BorderSide(
|
|
color: FlutterFlowTheme.of(context).primaryBackground,
|
|
width: 1,
|
|
),
|
|
splashColor: const Color.fromARGB(255, 129, 129, 129),
|
|
borderRadius: BorderRadius.circular(10),
|
|
elevation: 0,
|
|
),
|
|
text: FFLocalizations.of(context).getVariableText(
|
|
enText: 'No',
|
|
ptText: 'Não',
|
|
),
|
|
),
|
|
FFButtonWidget(
|
|
key: ValueKey<String>('AcceptOptionKey'),
|
|
onPressed: () async {
|
|
action();
|
|
},
|
|
options: FFButtonOptions(
|
|
width: MediaQuery.of(context).size.width * 0.3,
|
|
height: 50,
|
|
color: FlutterFlowTheme.of(context).primaryBackground,
|
|
elevation: 0,
|
|
textStyle: TextStyle(
|
|
color: FlutterFlowTheme.of(context).primaryText,
|
|
fontSize: limitedInputFontSize),
|
|
splashColor: const Color.fromARGB(255, 129, 129, 129),
|
|
borderSide: BorderSide(
|
|
color: FlutterFlowTheme.of(context).primaryBackground,
|
|
width: 1,
|
|
),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
text: FFLocalizations.of(context).getVariableText(
|
|
enText: 'Yes',
|
|
ptText: 'Sim',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
extension FFStringExt on String {
|
|
String maybeHandleOverflow({int? maxChars, String replacement = ''}) =>
|
|
maxChars != null && length > maxChars
|
|
? replaceRange(maxChars, null, replacement)
|
|
: this;
|
|
}
|
|
|
|
extension ListFilterExt<T> on Iterable<T?> {
|
|
List<T> get withoutNulls => where((s) => s != null).map((e) => e!).toList();
|
|
}
|
|
|
|
extension MapFilterExtensions<T> on Map<String, T?> {
|
|
Map<String, T> get withoutNulls => Map.fromEntries(
|
|
entries
|
|
.where((e) => e.value != null)
|
|
.map((e) => MapEntry(e.key, e.value as T)),
|
|
);
|
|
}
|
|
|
|
extension MapListContainsExt on List<dynamic> {
|
|
bool containsMap(dynamic map) => map is Map
|
|
? any((e) => e is Map && const DeepCollectionEquality().equals(e, map))
|
|
: contains(map);
|
|
}
|
|
|
|
extension ListDivideExt<T extends Widget> on Iterable<T> {
|
|
Iterable<MapEntry<int, Widget>> get enumerate => toList().asMap().entries;
|
|
|
|
List<Widget> divide(Widget t, {bool Function(int)? filterFn}) => isEmpty
|
|
? []
|
|
: (enumerate
|
|
.map((e) => [e.value, if (filterFn == null || filterFn(e.key)) t])
|
|
.expand((i) => i)
|
|
.toList()
|
|
..removeLast());
|
|
|
|
List<Widget> around(Widget t) => addToStart(t).addToEnd(t);
|
|
|
|
List<Widget> addToStart(Widget t) =>
|
|
enumerate.map((e) => e.value).toList()..insert(0, t);
|
|
|
|
List<Widget> addToEnd(Widget t) =>
|
|
enumerate.map((e) => e.value).toList()..add(t);
|
|
|
|
List<Padding> paddingTopEach(double val) =>
|
|
map((w) => Padding(padding: EdgeInsets.only(top: val), child: w))
|
|
.toList();
|
|
}
|
|
|
|
extension StatefulWidgetExtensions on State<StatefulWidget> {
|
|
/// Check if the widget exist before safely setting state.
|
|
void safeSetState(VoidCallback fn) {
|
|
if (mounted) {
|
|
// ignore: invalid_use_of_protected_member
|
|
setState(fn);
|
|
}
|
|
}
|
|
}
|
|
|
|
// For iOS 16 and below, set the status bar color to match the app's theme.
|
|
// https://github.com/flutter/flutter/issues/41067
|
|
Brightness? _lastBrightness;
|
|
|
|
void fixStatusBarOniOS16AndBelow(BuildContext context) {
|
|
if (!isiOS) {
|
|
return;
|
|
}
|
|
final brightness = Theme.of(context).brightness;
|
|
if (_lastBrightness != brightness) {
|
|
_lastBrightness = brightness;
|
|
SystemChrome.setSystemUIOverlayStyle(
|
|
SystemUiOverlayStyle(
|
|
statusBarBrightness: brightness,
|
|
systemStatusBarContrastEnforced: true,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
extension ListUniqueExt<T> on Iterable<T> {
|
|
List<T> unique(dynamic Function(T) getKey) {
|
|
var distinctSet = <dynamic>{};
|
|
var distinctList = <T>[];
|
|
for (var item in this) {
|
|
if (distinctSet.add(getKey(item))) {
|
|
distinctList.add(item);
|
|
}
|
|
}
|
|
return distinctList;
|
|
}
|
|
}
|
|
|
|
String roundTo(double value, int decimalPoints) {
|
|
final power = pow(10, decimalPoints);
|
|
return ((value * power).round() / power).toString();
|
|
}
|
|
|
|
double computeGradientAlignmentX(double evaluatedAngle) {
|
|
evaluatedAngle %= 360;
|
|
final rads = evaluatedAngle * pi / 180;
|
|
double x;
|
|
if (evaluatedAngle < 45 || evaluatedAngle > 315) {
|
|
x = sin(2 * rads);
|
|
} else if (45 <= evaluatedAngle && evaluatedAngle <= 135) {
|
|
x = 1;
|
|
} else if (135 <= evaluatedAngle && evaluatedAngle <= 225) {
|
|
x = sin(-2 * rads);
|
|
} else {
|
|
x = -1;
|
|
}
|
|
return double.parse(roundTo(x, 2));
|
|
}
|
|
|
|
double computeGradientAlignmentY(double evaluatedAngle) {
|
|
evaluatedAngle %= 360;
|
|
final rads = evaluatedAngle * pi / 180;
|
|
double y;
|
|
if (evaluatedAngle < 45 || evaluatedAngle > 315) {
|
|
y = -1;
|
|
} else if (45 <= evaluatedAngle && evaluatedAngle <= 135) {
|
|
y = sin(-2 * rads);
|
|
} else if (135 <= evaluatedAngle && evaluatedAngle <= 225) {
|
|
y = 1;
|
|
} else {
|
|
y = sin(2 * rads);
|
|
}
|
|
return double.parse(roundTo(y, 2));
|
|
}
|