Add application source code and update project structure
This commit is contained in:
112
Application Product/Source/source/lib/flutter_flow/flutter_flow_animations.dart
Executable file
112
Application Product/Source/source/lib/flutter_flow/flutter_flow_animations.dart
Executable file
@@ -0,0 +1,112 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
|
||||
enum AnimationTrigger {
|
||||
onPageLoad,
|
||||
onActionTrigger,
|
||||
}
|
||||
|
||||
class AnimationInfo {
|
||||
AnimationInfo({
|
||||
required this.trigger,
|
||||
required this.effectsBuilder,
|
||||
this.loop = false,
|
||||
this.reverse = false,
|
||||
this.applyInitialState = true,
|
||||
});
|
||||
final AnimationTrigger trigger;
|
||||
final List<Effect> Function()? effectsBuilder;
|
||||
final bool applyInitialState;
|
||||
final bool loop;
|
||||
final bool reverse;
|
||||
late AnimationController controller;
|
||||
|
||||
List<Effect>? _effects;
|
||||
List<Effect> get effects => _effects ??= effectsBuilder!();
|
||||
|
||||
void maybeUpdateEffects(List<Effect>? updatedEffects) {
|
||||
if (updatedEffects != null) {
|
||||
_effects = updatedEffects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void createAnimation(AnimationInfo animation, TickerProvider vsync) {
|
||||
final newController = AnimationController(vsync: vsync);
|
||||
animation.controller = newController;
|
||||
}
|
||||
|
||||
void setupAnimations(Iterable<AnimationInfo> animations, TickerProvider vsync) {
|
||||
animations.forEach((animation) => createAnimation(animation, vsync));
|
||||
}
|
||||
|
||||
extension AnimatedWidgetExtension on Widget {
|
||||
Widget animateOnPageLoad(
|
||||
AnimationInfo animationInfo, {
|
||||
List<Effect>? effects,
|
||||
}) {
|
||||
animationInfo.maybeUpdateEffects(effects);
|
||||
return Animate(
|
||||
effects: animationInfo.effects,
|
||||
child: this,
|
||||
onPlay: (controller) => animationInfo.loop
|
||||
? controller.repeat(reverse: animationInfo.reverse)
|
||||
: null,
|
||||
onComplete: (controller) => !animationInfo.loop && animationInfo.reverse
|
||||
? controller.reverse()
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Widget animateOnActionTrigger(
|
||||
AnimationInfo animationInfo, {
|
||||
List<Effect>? effects,
|
||||
bool hasBeenTriggered = false,
|
||||
}) {
|
||||
animationInfo.maybeUpdateEffects(effects);
|
||||
return hasBeenTriggered || animationInfo.applyInitialState
|
||||
? Animate(
|
||||
controller: animationInfo.controller,
|
||||
autoPlay: false,
|
||||
effects: animationInfo.effects,
|
||||
child: this)
|
||||
: this;
|
||||
}
|
||||
}
|
||||
|
||||
class TiltEffect extends Effect<Offset> {
|
||||
const TiltEffect({
|
||||
Duration? delay,
|
||||
Duration? duration,
|
||||
Curve? curve,
|
||||
Offset? begin,
|
||||
Offset? end,
|
||||
}) : super(
|
||||
delay: delay,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
begin: begin ?? const Offset(0.0, 0.0),
|
||||
end: end ?? const Offset(0.0, 0.0),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
Widget child,
|
||||
AnimationController controller,
|
||||
EffectEntry entry,
|
||||
) {
|
||||
Animation<Offset> animation = buildAnimation(controller, entry);
|
||||
return getOptimizedBuilder<Offset>(
|
||||
animation: animation,
|
||||
builder: (_, __) => Transform(
|
||||
transform: Matrix4.identity()
|
||||
..setEntry(3, 2, 0.001)
|
||||
..rotateX(animation.value.dx)
|
||||
..rotateY(animation.value.dy),
|
||||
alignment: Alignment.center,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
325
Application Product/Source/source/lib/flutter_flow/flutter_flow_calendar.dart
Executable file
325
Application Product/Source/source/lib/flutter_flow/flutter_flow_calendar.dart
Executable file
@@ -0,0 +1,325 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
DateTime kFirstDay = DateTime(1970, 1, 1);
|
||||
DateTime kLastDay = DateTime(2100, 1, 1);
|
||||
|
||||
extension DateTimeExtension on DateTime {
|
||||
DateTime get startOfDay => DateTime(year, month, day);
|
||||
|
||||
DateTime get endOfDay => DateTime(year, month, day, 23, 59);
|
||||
}
|
||||
|
||||
class FlutterFlowCalendar extends StatefulWidget {
|
||||
const FlutterFlowCalendar({
|
||||
Key? key,
|
||||
required this.color,
|
||||
this.onChange,
|
||||
this.initialDate,
|
||||
this.weekFormat = false,
|
||||
this.weekStartsMonday = false,
|
||||
this.twoRowHeader = false,
|
||||
this.iconColor,
|
||||
this.dateStyle,
|
||||
this.dayOfWeekStyle,
|
||||
this.inactiveDateStyle,
|
||||
this.selectedDateStyle,
|
||||
this.titleStyle,
|
||||
this.rowHeight,
|
||||
this.locale,
|
||||
}) : super(key: key);
|
||||
|
||||
final bool weekFormat;
|
||||
final bool weekStartsMonday;
|
||||
final bool twoRowHeader;
|
||||
final Color color;
|
||||
final void Function(DateTimeRange?)? onChange;
|
||||
final DateTime? initialDate;
|
||||
final Color? iconColor;
|
||||
final TextStyle? dateStyle;
|
||||
final TextStyle? dayOfWeekStyle;
|
||||
final TextStyle? inactiveDateStyle;
|
||||
final TextStyle? selectedDateStyle;
|
||||
final TextStyle? titleStyle;
|
||||
final double? rowHeight;
|
||||
final String? locale;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _FlutterFlowCalendarState();
|
||||
}
|
||||
|
||||
class _FlutterFlowCalendarState extends State<FlutterFlowCalendar> {
|
||||
late DateTime focusedDay;
|
||||
late DateTime selectedDay;
|
||||
late DateTimeRange selectedRange;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
focusedDay = widget.initialDate ?? DateTime.now();
|
||||
selectedDay = widget.initialDate ?? DateTime.now();
|
||||
selectedRange = DateTimeRange(
|
||||
start: selectedDay.startOfDay,
|
||||
end: selectedDay.endOfDay,
|
||||
);
|
||||
SchedulerBinding.instance
|
||||
.addPostFrameCallback((_) => setSelectedDay(selectedRange.start));
|
||||
}
|
||||
|
||||
CalendarFormat get calendarFormat =>
|
||||
widget.weekFormat ? CalendarFormat.week : CalendarFormat.month;
|
||||
|
||||
StartingDayOfWeek get startingDayOfWeek => widget.weekStartsMonday
|
||||
? StartingDayOfWeek.monday
|
||||
: StartingDayOfWeek.sunday;
|
||||
|
||||
Color get color => widget.color;
|
||||
|
||||
Color get lightColor => widget.color.withOpacity(0.85);
|
||||
|
||||
Color get lighterColor => widget.color.withOpacity(0.60);
|
||||
|
||||
void setSelectedDay(
|
||||
DateTime? newSelectedDay, [
|
||||
DateTime? newSelectedEnd,
|
||||
]) {
|
||||
final newRange = newSelectedDay == null
|
||||
? null
|
||||
: DateTimeRange(
|
||||
start: newSelectedDay.startOfDay,
|
||||
end: newSelectedEnd ?? newSelectedDay.endOfDay,
|
||||
);
|
||||
setState(() {
|
||||
selectedDay = newSelectedDay ?? selectedDay;
|
||||
selectedRange = newRange ?? selectedRange;
|
||||
if (widget.onChange != null) {
|
||||
widget.onChange!(newRange);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
CalendarHeader(
|
||||
focusedDay: focusedDay,
|
||||
onLeftChevronTap: () => setState(
|
||||
() => focusedDay = widget.weekFormat
|
||||
? _previousWeek(focusedDay)
|
||||
: _previousMonth(focusedDay),
|
||||
),
|
||||
onRightChevronTap: () => setState(
|
||||
() => focusedDay = widget.weekFormat
|
||||
? _nextWeek(focusedDay)
|
||||
: _nextMonth(focusedDay),
|
||||
),
|
||||
onTodayButtonTap: () => setState(() => focusedDay = DateTime.now()),
|
||||
titleStyle: widget.titleStyle,
|
||||
iconColor: widget.iconColor,
|
||||
locale: widget.locale,
|
||||
twoRowHeader: widget.twoRowHeader,
|
||||
),
|
||||
TableCalendar(
|
||||
focusedDay: focusedDay,
|
||||
selectedDayPredicate: (date) => isSameDay(selectedDay, date),
|
||||
firstDay: kFirstDay,
|
||||
lastDay: kLastDay,
|
||||
calendarFormat: calendarFormat,
|
||||
headerVisible: false,
|
||||
locale: widget.locale,
|
||||
rowHeight: widget.rowHeight ?? MediaQuery.sizeOf(context).width / 7,
|
||||
calendarStyle: CalendarStyle(
|
||||
defaultTextStyle:
|
||||
widget.dateStyle ?? const TextStyle(color: Color(0xFF5A5A5A)),
|
||||
weekendTextStyle: widget.dateStyle ??
|
||||
const TextStyle(color: const Color(0xFF5A5A5A)),
|
||||
holidayTextStyle: widget.dateStyle ??
|
||||
const TextStyle(color: const Color(0xFF5C6BC0)),
|
||||
selectedTextStyle:
|
||||
const TextStyle(color: Color(0xFFFAFAFA), fontSize: 16.0)
|
||||
.merge(widget.selectedDateStyle),
|
||||
todayTextStyle:
|
||||
const TextStyle(color: Color(0xFFFAFAFA), fontSize: 16.0)
|
||||
.merge(widget.selectedDateStyle),
|
||||
outsideTextStyle: const TextStyle(color: Color(0xFF9E9E9E))
|
||||
.merge(widget.inactiveDateStyle),
|
||||
selectedDecoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
todayDecoration: BoxDecoration(
|
||||
color: lighterColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
markerDecoration: BoxDecoration(
|
||||
color: lightColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
markersMaxCount: 3,
|
||||
canMarkersOverflow: true,
|
||||
),
|
||||
availableGestures: AvailableGestures.horizontalSwipe,
|
||||
startingDayOfWeek: startingDayOfWeek,
|
||||
daysOfWeekStyle: DaysOfWeekStyle(
|
||||
weekdayStyle: const TextStyle(color: Color(0xFF616161))
|
||||
.merge(widget.dayOfWeekStyle),
|
||||
weekendStyle: const TextStyle(color: Color(0xFF616161))
|
||||
.merge(widget.dayOfWeekStyle),
|
||||
),
|
||||
onPageChanged: (focused) {
|
||||
if (focusedDay.startOfDay != focused.startOfDay) {
|
||||
setState(() => focusedDay = focused);
|
||||
}
|
||||
},
|
||||
onDaySelected: (newSelectedDay, focused) {
|
||||
if (!isSameDay(selectedDay, newSelectedDay)) {
|
||||
setSelectedDay(newSelectedDay);
|
||||
if (focusedDay.startOfDay != focused.startOfDay) {
|
||||
setState(() => focusedDay = focused);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class CalendarHeader extends StatelessWidget {
|
||||
const CalendarHeader({
|
||||
Key? key,
|
||||
required this.focusedDay,
|
||||
required this.onLeftChevronTap,
|
||||
required this.onRightChevronTap,
|
||||
required this.onTodayButtonTap,
|
||||
this.iconColor,
|
||||
this.titleStyle,
|
||||
this.locale,
|
||||
this.twoRowHeader = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final DateTime focusedDay;
|
||||
final VoidCallback onLeftChevronTap;
|
||||
final VoidCallback onRightChevronTap;
|
||||
final VoidCallback onTodayButtonTap;
|
||||
final Color? iconColor;
|
||||
final TextStyle? titleStyle;
|
||||
final String? locale;
|
||||
final bool twoRowHeader;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
decoration: const BoxDecoration(),
|
||||
margin: const EdgeInsets.all(0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: twoRowHeader ? _buildTwoRowHeader() : _buildOneRowHeader(),
|
||||
);
|
||||
|
||||
Widget _buildTwoRowHeader() => Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
_buildDateWidget(),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: _buildCustomIconButtons(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildOneRowHeader() => Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
const SizedBox(width: 16),
|
||||
_buildDateWidget(),
|
||||
..._buildCustomIconButtons(),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildDateWidget() => Expanded(
|
||||
child: Text(
|
||||
DateFormat.yMMMM(locale).format(focusedDay),
|
||||
style: const TextStyle(fontSize: 17).merge(titleStyle),
|
||||
),
|
||||
);
|
||||
|
||||
List<Widget> _buildCustomIconButtons() => <Widget>[
|
||||
CustomIconButton(
|
||||
icon: Icon(Icons.calendar_today, color: iconColor),
|
||||
onTap: onTodayButtonTap,
|
||||
),
|
||||
CustomIconButton(
|
||||
icon: Icon(Icons.chevron_left, color: iconColor),
|
||||
onTap: onLeftChevronTap,
|
||||
),
|
||||
CustomIconButton(
|
||||
icon: Icon(Icons.chevron_right, color: iconColor),
|
||||
onTap: onRightChevronTap,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
class CustomIconButton extends StatelessWidget {
|
||||
const CustomIconButton({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
this.margin = const EdgeInsets.symmetric(horizontal: 4),
|
||||
this.padding = const EdgeInsets.all(10),
|
||||
}) : super(key: key);
|
||||
|
||||
final Icon icon;
|
||||
final VoidCallback onTap;
|
||||
final EdgeInsetsGeometry margin;
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: margin,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: Icon(
|
||||
icon.icon,
|
||||
color: icon.color,
|
||||
size: icon.size,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
DateTime _previousWeek(DateTime week) {
|
||||
return week.subtract(const Duration(days: 7));
|
||||
}
|
||||
|
||||
DateTime _nextWeek(DateTime week) {
|
||||
return week.add(const Duration(days: 7));
|
||||
}
|
||||
|
||||
DateTime _previousMonth(DateTime month) {
|
||||
if (month.month == 1) {
|
||||
return DateTime(month.year - 1, 12);
|
||||
} else {
|
||||
return DateTime(month.year, month.month - 1);
|
||||
}
|
||||
}
|
||||
|
||||
DateTime _nextMonth(DateTime month) {
|
||||
if (month.month == 12) {
|
||||
return DateTime(month.year + 1, 1);
|
||||
} else {
|
||||
return DateTime(month.year, month.month + 1);
|
||||
}
|
||||
}
|
||||
547
Application Product/Source/source/lib/flutter_flow/flutter_flow_charts.dart
Executable file
547
Application Product/Source/source/lib/flutter_flow/flutter_flow_charts.dart
Executable file
@@ -0,0 +1,547 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
export 'package:fl_chart/fl_chart.dart'
|
||||
show BarAreaData, FlDotData, LineChartBarData, BarChartAlignment;
|
||||
|
||||
class FlutterFlowLineChart extends StatelessWidget {
|
||||
const FlutterFlowLineChart({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required this.xAxisLabelInfo,
|
||||
required this.yAxisLabelInfo,
|
||||
required this.axisBounds,
|
||||
this.chartStylingInfo = const ChartStylingInfo(),
|
||||
}) : super(key: key);
|
||||
|
||||
final List<FFLineChartData> data;
|
||||
final AxisLabelInfo xAxisLabelInfo;
|
||||
final AxisLabelInfo yAxisLabelInfo;
|
||||
final AxisBounds axisBounds;
|
||||
final ChartStylingInfo chartStylingInfo;
|
||||
|
||||
List<LineChartBarData> get dataWithSpots =>
|
||||
data.map((d) => d.settings.copyWith(spots: d.spots)).toList();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(
|
||||
handleBuiltInTouches: chartStylingInfo.enableTooltip,
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
getTooltipColor: (group) =>
|
||||
chartStylingInfo.tooltipBackgroundColor ?? Colors.black,
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(show: chartStylingInfo.showGrid),
|
||||
borderData: FlBorderData(
|
||||
border: Border.all(
|
||||
color: chartStylingInfo.borderColor,
|
||||
width: chartStylingInfo.borderWidth,
|
||||
),
|
||||
show: chartStylingInfo.showBorder,
|
||||
),
|
||||
titlesData: getTitlesData(
|
||||
xAxisLabelInfo,
|
||||
yAxisLabelInfo,
|
||||
),
|
||||
lineBarsData: dataWithSpots,
|
||||
minX: axisBounds.minX,
|
||||
minY: axisBounds.minY,
|
||||
maxX: axisBounds.maxX,
|
||||
maxY: axisBounds.maxY,
|
||||
backgroundColor: chartStylingInfo.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class FlutterFlowBarChart extends StatelessWidget {
|
||||
const FlutterFlowBarChart({
|
||||
Key? key,
|
||||
required this.barData,
|
||||
required this.xLabels,
|
||||
required this.xAxisLabelInfo,
|
||||
required this.yAxisLabelInfo,
|
||||
required this.axisBounds,
|
||||
this.stacked = false,
|
||||
this.barWidth,
|
||||
this.barBorderRadius,
|
||||
this.barSpace,
|
||||
this.groupSpace,
|
||||
this.alignment = BarChartAlignment.center,
|
||||
this.chartStylingInfo = const ChartStylingInfo(),
|
||||
}) : super(key: key);
|
||||
|
||||
final List<FFBarChartData> barData;
|
||||
final List<String> xLabels;
|
||||
final AxisLabelInfo xAxisLabelInfo;
|
||||
final AxisLabelInfo yAxisLabelInfo;
|
||||
final AxisBounds axisBounds;
|
||||
final bool stacked;
|
||||
final double? barWidth;
|
||||
final BorderRadius? barBorderRadius;
|
||||
final double? barSpace;
|
||||
final double? groupSpace;
|
||||
final BarChartAlignment alignment;
|
||||
final ChartStylingInfo chartStylingInfo;
|
||||
|
||||
Map<int, List<double>> get dataMap => xLabels.asMap().map((key, value) =>
|
||||
MapEntry(key, barData.map((data) => data.data[key]).toList()));
|
||||
|
||||
List<BarChartGroupData> get groups => dataMap.entries.map((entry) {
|
||||
final groupInt = entry.key;
|
||||
final groupData = entry.value;
|
||||
return BarChartGroupData(
|
||||
x: groupInt,
|
||||
barsSpace: barSpace,
|
||||
barRods: groupData.asMap().entries.map((rod) {
|
||||
final rodInt = rod.key;
|
||||
final rodSettings = barData[rodInt];
|
||||
final rodValue = rod.value;
|
||||
return BarChartRodData(
|
||||
toY: rodValue,
|
||||
color: rodSettings.color,
|
||||
width: barWidth,
|
||||
borderRadius: barBorderRadius,
|
||||
borderSide: BorderSide(
|
||||
width: rodSettings.borderWidth,
|
||||
color: rodSettings.borderColor,
|
||||
),
|
||||
);
|
||||
}).toList());
|
||||
}).toList();
|
||||
|
||||
List<BarChartGroupData> get stacks => dataMap.entries.map((entry) {
|
||||
final groupInt = entry.key;
|
||||
final stackData = entry.value;
|
||||
return BarChartGroupData(
|
||||
x: groupInt,
|
||||
barsSpace: barSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: sum(stackData),
|
||||
width: barWidth,
|
||||
borderRadius: barBorderRadius,
|
||||
rodStackItems: stackData.asMap().entries.map((stack) {
|
||||
final stackInt = stack.key;
|
||||
final stackSettings = barData[stackInt];
|
||||
final start =
|
||||
stackInt == 0 ? 0.0 : sum(stackData.sublist(0, stackInt));
|
||||
return BarChartRodStackItem(
|
||||
start,
|
||||
start + stack.value,
|
||||
stackSettings.color,
|
||||
BorderSide(
|
||||
width: stackSettings.borderWidth,
|
||||
color: stackSettings.borderColor,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
)
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
|
||||
double sum(List<double> list) => list.reduce((a, b) => a + b);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
barTouchData: BarTouchData(
|
||||
handleBuiltInTouches: chartStylingInfo.enableTooltip,
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipColor: (group) =>
|
||||
chartStylingInfo.tooltipBackgroundColor ?? Colors.black,
|
||||
),
|
||||
),
|
||||
alignment: alignment,
|
||||
gridData: FlGridData(show: chartStylingInfo.showGrid),
|
||||
borderData: FlBorderData(
|
||||
border: Border.all(
|
||||
color: chartStylingInfo.borderColor,
|
||||
width: chartStylingInfo.borderWidth,
|
||||
),
|
||||
show: chartStylingInfo.showBorder,
|
||||
),
|
||||
titlesData: getTitlesData(
|
||||
xAxisLabelInfo,
|
||||
yAxisLabelInfo,
|
||||
getXTitlesWidget: (val, _) => Text(
|
||||
xLabels[val.toInt()],
|
||||
style: xAxisLabelInfo.labelTextStyle,
|
||||
),
|
||||
),
|
||||
barGroups: stacked ? stacks : groups,
|
||||
groupsSpace: groupSpace,
|
||||
minY: axisBounds.minY,
|
||||
maxY: axisBounds.maxY,
|
||||
backgroundColor: chartStylingInfo.backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum PieChartSectionLabelType {
|
||||
none,
|
||||
value,
|
||||
percent,
|
||||
}
|
||||
|
||||
class FlutterFlowPieChart extends StatelessWidget {
|
||||
const FlutterFlowPieChart({
|
||||
Key? key,
|
||||
required this.data,
|
||||
this.donutHoleRadius = 0,
|
||||
this.donutHoleColor = Colors.transparent,
|
||||
this.sectionLabelType = PieChartSectionLabelType.none,
|
||||
this.sectionLabelStyle,
|
||||
this.labelFormatter = const LabelFormatter(),
|
||||
}) : super(key: key);
|
||||
|
||||
final FFPieChartData data;
|
||||
final double donutHoleRadius;
|
||||
final Color donutHoleColor;
|
||||
final PieChartSectionLabelType sectionLabelType;
|
||||
final TextStyle? sectionLabelStyle;
|
||||
final LabelFormatter labelFormatter;
|
||||
|
||||
double get sumOfValues => data.data.reduce((a, b) => a + b);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => PieChart(
|
||||
PieChartData(
|
||||
centerSpaceRadius: donutHoleRadius,
|
||||
centerSpaceColor: donutHoleColor,
|
||||
sectionsSpace: 0,
|
||||
sections: data.data.asMap().entries.map(
|
||||
(section) {
|
||||
String? title;
|
||||
final index = section.key;
|
||||
final sectionData = section.value;
|
||||
final colorsLength = data.colors.length;
|
||||
final otherPropsLength = data.radius.length;
|
||||
switch (sectionLabelType) {
|
||||
case PieChartSectionLabelType.value:
|
||||
title = formatLabel(labelFormatter, sectionData);
|
||||
break;
|
||||
case PieChartSectionLabelType.percent:
|
||||
title =
|
||||
'${formatLabel(labelFormatter, sectionData / sumOfValues * 100)}%';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return PieChartSectionData(
|
||||
value: sectionData,
|
||||
color: data.colors[index % colorsLength],
|
||||
radius: otherPropsLength == 1
|
||||
? data.radius.first
|
||||
: data.radius[index],
|
||||
borderSide: BorderSide(
|
||||
color: (otherPropsLength == 1
|
||||
? data.borderColor?.first
|
||||
: data.borderColor?.elementAt(index)) ??
|
||||
Colors.transparent,
|
||||
width: (otherPropsLength == 1
|
||||
? data.borderWidth?.first
|
||||
: data.borderWidth?.elementAt(index)) ??
|
||||
0.0,
|
||||
),
|
||||
showTitle: sectionLabelType != PieChartSectionLabelType.none,
|
||||
titleStyle: sectionLabelStyle,
|
||||
title: title,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class FlutterFlowChartLegendWidget extends StatelessWidget {
|
||||
const FlutterFlowChartLegendWidget({
|
||||
Key? key,
|
||||
required this.entries,
|
||||
this.width,
|
||||
this.height,
|
||||
this.textStyle,
|
||||
this.padding,
|
||||
this.backgroundColor = Colors.transparent,
|
||||
this.borderRadius,
|
||||
this.borderWidth = 1.0,
|
||||
this.borderColor = const Color(0xFF000000),
|
||||
this.indicatorSize = 10,
|
||||
this.indicatorBorderRadius,
|
||||
this.textPadding = const EdgeInsets.all(0),
|
||||
}) : super(key: key);
|
||||
|
||||
final List<LegendEntry> entries;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final TextStyle? textStyle;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final Color backgroundColor;
|
||||
final BorderRadius? borderRadius;
|
||||
final double borderWidth;
|
||||
final Color borderColor;
|
||||
final double indicatorSize;
|
||||
final BorderRadius? indicatorBorderRadius;
|
||||
final EdgeInsetsGeometry textPadding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
width: width,
|
||||
height: height,
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: borderRadius,
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
width: borderWidth,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: entries
|
||||
.map(
|
||||
(entry) => Row(
|
||||
children: [
|
||||
Container(
|
||||
height: indicatorSize,
|
||||
width: indicatorSize,
|
||||
decoration: BoxDecoration(
|
||||
color: entry.color,
|
||||
borderRadius: indicatorBorderRadius,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: textPadding,
|
||||
child: Text(
|
||||
entry.name,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class LegendEntry {
|
||||
const LegendEntry(this.color, this.name);
|
||||
|
||||
final Color color;
|
||||
final String name;
|
||||
}
|
||||
|
||||
class ChartStylingInfo {
|
||||
const ChartStylingInfo({
|
||||
this.backgroundColor = Colors.white,
|
||||
this.showGrid = false,
|
||||
this.enableTooltip = false,
|
||||
this.tooltipBackgroundColor,
|
||||
this.borderColor = Colors.black,
|
||||
this.borderWidth = 1.0,
|
||||
this.showBorder = true,
|
||||
});
|
||||
|
||||
final Color backgroundColor;
|
||||
final bool showGrid;
|
||||
final bool enableTooltip;
|
||||
final Color? tooltipBackgroundColor;
|
||||
final Color borderColor;
|
||||
final double borderWidth;
|
||||
final bool showBorder;
|
||||
}
|
||||
|
||||
class AxisLabelInfo {
|
||||
const AxisLabelInfo({
|
||||
this.title = '',
|
||||
this.titleTextStyle,
|
||||
this.showLabels = false,
|
||||
this.labelTextStyle,
|
||||
this.labelInterval,
|
||||
this.labelFormatter = const LabelFormatter(),
|
||||
this.reservedSize,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final TextStyle? titleTextStyle;
|
||||
final bool showLabels;
|
||||
final TextStyle? labelTextStyle;
|
||||
final double? labelInterval;
|
||||
final LabelFormatter labelFormatter;
|
||||
final double? reservedSize;
|
||||
}
|
||||
|
||||
class LabelFormatter {
|
||||
const LabelFormatter({
|
||||
this.numberFormat,
|
||||
});
|
||||
|
||||
final String Function(double)? numberFormat;
|
||||
NumberFormat get defaultFormat => NumberFormat()..significantDigits = 2;
|
||||
}
|
||||
|
||||
class AxisBounds {
|
||||
const AxisBounds({this.minX, this.minY, this.maxX, this.maxY});
|
||||
|
||||
final double? minX;
|
||||
final double? minY;
|
||||
final double? maxX;
|
||||
final double? maxY;
|
||||
}
|
||||
|
||||
class FFLineChartData {
|
||||
const FFLineChartData({
|
||||
required this.xData,
|
||||
required this.yData,
|
||||
required this.settings,
|
||||
});
|
||||
|
||||
final List<dynamic> xData;
|
||||
final List<dynamic> yData;
|
||||
final LineChartBarData settings;
|
||||
|
||||
List<FlSpot> get spots {
|
||||
final x = _dataToDouble(xData);
|
||||
final y = _dataToDouble(yData);
|
||||
assert(x.length == y.length, 'X and Y data must be the same length');
|
||||
|
||||
return Iterable<int>.generate(min(x.length, y.length))
|
||||
.where((i) => x[i] != null && y[i] != null)
|
||||
.map((i) => FlSpot(x[i]!, y[i]!))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class FFBarChartData {
|
||||
const FFBarChartData({
|
||||
required this.yData,
|
||||
required this.color,
|
||||
this.borderWidth = 0,
|
||||
this.borderColor = Colors.transparent,
|
||||
});
|
||||
|
||||
final List<dynamic> yData;
|
||||
final Color color;
|
||||
final double borderWidth;
|
||||
final Color borderColor;
|
||||
|
||||
List<double> get data => _dataToDouble(yData).map((e) => e ?? 0.0).toList();
|
||||
}
|
||||
|
||||
class FFPieChartData {
|
||||
const FFPieChartData({
|
||||
required this.values,
|
||||
required this.colors,
|
||||
required this.radius,
|
||||
this.borderWidth,
|
||||
this.borderColor,
|
||||
});
|
||||
|
||||
final List<dynamic> values;
|
||||
final List<Color> colors;
|
||||
final List<double> radius;
|
||||
final List<double>? borderWidth;
|
||||
final List<Color>? borderColor;
|
||||
|
||||
List<double> get data => _dataToDouble(values).map((e) => e ?? 0.0).toList();
|
||||
}
|
||||
|
||||
List<double?> _dataToDouble(List<dynamic> data) {
|
||||
if (data.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
if (data.first is double) {
|
||||
return data.map((d) => d as double).toList();
|
||||
}
|
||||
if (data.first is int) {
|
||||
return data.map((d) => (d as int).toDouble()).toList();
|
||||
}
|
||||
if (data.first is DateTime) {
|
||||
return data
|
||||
.map((d) => (d as DateTime).millisecondsSinceEpoch.toDouble())
|
||||
.toList();
|
||||
}
|
||||
if (data.first is String) {
|
||||
// First try to parse as doubles
|
||||
if (double.tryParse(data.first as String) != null) {
|
||||
return data.map((d) => double.tryParse(d as String)).toList();
|
||||
}
|
||||
if (int.tryParse(data.first as String) != null) {
|
||||
return data.map((d) => int.tryParse(d as String)?.toDouble()).toList();
|
||||
}
|
||||
if (DateTime.tryParse(data.first as String) != null) {
|
||||
return data
|
||||
.map((d) =>
|
||||
DateTime.tryParse(d as String)?.millisecondsSinceEpoch.toDouble())
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
FlTitlesData getTitlesData(
|
||||
AxisLabelInfo xAxisLabelInfo,
|
||||
AxisLabelInfo yAxisLabelInfo, {
|
||||
Widget Function(double, TitleMeta)? getXTitlesWidget,
|
||||
}) =>
|
||||
FlTitlesData(
|
||||
bottomTitles: AxisTitles(
|
||||
axisNameWidget: xAxisLabelInfo.title.isEmpty
|
||||
? null
|
||||
: Text(
|
||||
xAxisLabelInfo.title,
|
||||
style: xAxisLabelInfo.titleTextStyle,
|
||||
),
|
||||
axisNameSize: xAxisLabelInfo.titleTextStyle?.fontSize != null
|
||||
? xAxisLabelInfo.titleTextStyle!.fontSize! + 12
|
||||
: 16,
|
||||
sideTitles: SideTitles(
|
||||
getTitlesWidget: (val, _) => getXTitlesWidget != null
|
||||
? getXTitlesWidget(val, _)
|
||||
: Text(
|
||||
formatLabel(xAxisLabelInfo.labelFormatter, val),
|
||||
style: xAxisLabelInfo.labelTextStyle,
|
||||
),
|
||||
showTitles: xAxisLabelInfo.showLabels,
|
||||
interval: xAxisLabelInfo.labelInterval,
|
||||
reservedSize: xAxisLabelInfo.reservedSize ?? 22,
|
||||
),
|
||||
),
|
||||
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
leftTitles: AxisTitles(
|
||||
axisNameWidget: yAxisLabelInfo.title.isEmpty
|
||||
? null
|
||||
: Text(
|
||||
yAxisLabelInfo.title,
|
||||
style: yAxisLabelInfo.titleTextStyle,
|
||||
),
|
||||
axisNameSize: yAxisLabelInfo.titleTextStyle?.fontSize != null
|
||||
? yAxisLabelInfo.titleTextStyle!.fontSize! + 12
|
||||
: 16,
|
||||
sideTitles: SideTitles(
|
||||
getTitlesWidget: (val, _) => Text(
|
||||
formatLabel(yAxisLabelInfo.labelFormatter, val),
|
||||
style: yAxisLabelInfo.labelTextStyle,
|
||||
),
|
||||
showTitles: yAxisLabelInfo.showLabels,
|
||||
interval: yAxisLabelInfo.labelInterval,
|
||||
reservedSize: yAxisLabelInfo.reservedSize ?? 22,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
String formatLabel(LabelFormatter formatter, double value) {
|
||||
if (formatter.numberFormat != null) {
|
||||
return formatter.numberFormat!(value);
|
||||
}
|
||||
return formatter.defaultFormat.format(value);
|
||||
}
|
||||
170
Application Product/Source/source/lib/flutter_flow/flutter_flow_icon_button.dart
Executable file
170
Application Product/Source/source/lib/flutter_flow/flutter_flow_icon_button.dart
Executable file
@@ -0,0 +1,170 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
|
||||
class FlutterFlowIconButton extends StatefulWidget {
|
||||
const FlutterFlowIconButton({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
this.borderColor,
|
||||
this.borderRadius,
|
||||
this.borderWidth,
|
||||
this.buttonSize,
|
||||
this.fillColor,
|
||||
this.disabledColor,
|
||||
this.disabledIconColor,
|
||||
this.hoverColor,
|
||||
this.hoverIconColor,
|
||||
this.onPressed,
|
||||
this.showLoadingIndicator = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget icon;
|
||||
final double? borderRadius;
|
||||
final double? buttonSize;
|
||||
final Color? fillColor;
|
||||
final Color? disabledColor;
|
||||
final Color? disabledIconColor;
|
||||
final Color? hoverColor;
|
||||
final Color? hoverIconColor;
|
||||
final Color? borderColor;
|
||||
final double? borderWidth;
|
||||
final bool showLoadingIndicator;
|
||||
final Function()? onPressed;
|
||||
|
||||
@override
|
||||
State<FlutterFlowIconButton> createState() => _FlutterFlowIconButtonState();
|
||||
}
|
||||
|
||||
class _FlutterFlowIconButtonState extends State<FlutterFlowIconButton> {
|
||||
bool loading = false;
|
||||
late double? iconSize;
|
||||
late Color? iconColor;
|
||||
late Widget effectiveIcon;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateIcon();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(FlutterFlowIconButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_updateIcon();
|
||||
}
|
||||
|
||||
void _updateIcon() {
|
||||
final isFontAwesome = widget.icon is FaIcon;
|
||||
if (isFontAwesome) {
|
||||
FaIcon icon = widget.icon as FaIcon;
|
||||
effectiveIcon = FaIcon(
|
||||
icon.icon,
|
||||
size: icon.size,
|
||||
);
|
||||
iconSize = icon.size;
|
||||
iconColor = icon.color;
|
||||
} else {
|
||||
Icon icon = widget.icon as Icon;
|
||||
effectiveIcon = Icon(
|
||||
icon.icon,
|
||||
size: icon.size,
|
||||
);
|
||||
iconSize = icon.size;
|
||||
iconColor = icon.color;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ButtonStyle style = ButtonStyle(
|
||||
shape: MaterialStateProperty.resolveWith<OutlinedBorder>(
|
||||
(states) {
|
||||
return RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius ?? 0),
|
||||
side: BorderSide(
|
||||
color: widget.borderColor ?? Colors.transparent,
|
||||
width: widget.borderWidth ?? 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
iconColor: MaterialStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.disabled) &&
|
||||
widget.disabledIconColor != null) {
|
||||
return widget.disabledIconColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered) &&
|
||||
widget.hoverIconColor != null) {
|
||||
return widget.hoverIconColor;
|
||||
}
|
||||
return iconColor;
|
||||
},
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.disabled) &&
|
||||
widget.disabledColor != null) {
|
||||
return widget.disabledColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered) &&
|
||||
widget.hoverColor != null) {
|
||||
return widget.hoverColor;
|
||||
}
|
||||
|
||||
return widget.fillColor;
|
||||
},
|
||||
),
|
||||
overlayColor: MaterialStateProperty.resolveWith<Color?>((states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return null;
|
||||
}
|
||||
return widget.hoverColor == null ? null : Colors.transparent;
|
||||
}),
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
width: widget.buttonSize,
|
||||
height: widget.buttonSize,
|
||||
child: Theme(
|
||||
data: ThemeData.from(
|
||||
colorScheme: Theme.of(context).colorScheme,
|
||||
useMaterial3: true,
|
||||
),
|
||||
child: IgnorePointer(
|
||||
ignoring: (widget.showLoadingIndicator && loading),
|
||||
child: IconButton(
|
||||
icon: (widget.showLoadingIndicator && loading)
|
||||
? Container(
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
iconColor ?? Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
: effectiveIcon,
|
||||
onPressed: widget.onPressed == null
|
||||
? null
|
||||
: () async {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setState(() => loading = true);
|
||||
try {
|
||||
await widget.onPressed!();
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
}
|
||||
},
|
||||
splashRadius: widget.buttonSize,
|
||||
style: style,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
168
Application Product/Source/source/lib/flutter_flow/flutter_flow_model.dart
Executable file
168
Application Product/Source/source/lib/flutter_flow/flutter_flow_model.dart
Executable file
@@ -0,0 +1,168 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'flutter_flow_util.dart';
|
||||
|
||||
Widget wrapWithModel<T extends FlutterFlowModel>({
|
||||
required T model,
|
||||
required Widget child,
|
||||
required VoidCallback updateCallback,
|
||||
bool updateOnChange = false,
|
||||
}) {
|
||||
// Set the component to optionally update the page on updates.
|
||||
model.setOnUpdate(
|
||||
onUpdate: updateCallback,
|
||||
updateOnChange: updateOnChange,
|
||||
);
|
||||
// Models for components within a page will be disposed by the page's model,
|
||||
// so we don't want the component widget to dispose them until the page is
|
||||
// itself disposed.
|
||||
model.disposeOnWidgetDisposal = false;
|
||||
// Wrap in a Provider so that the model can be accessed by the component.
|
||||
return Provider<T>.value(
|
||||
value: model,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
T createModel<T extends FlutterFlowModel>(
|
||||
BuildContext context,
|
||||
T Function() defaultBuilder,
|
||||
) {
|
||||
final model = context.read<T?>() ?? defaultBuilder();
|
||||
model._init(context);
|
||||
return model;
|
||||
}
|
||||
|
||||
abstract class FlutterFlowModel<W extends Widget> {
|
||||
// Initialization methods
|
||||
bool _isInitialized = false;
|
||||
void initState(BuildContext context);
|
||||
void _init(BuildContext context) {
|
||||
if (!_isInitialized) {
|
||||
initState(context);
|
||||
_isInitialized = true;
|
||||
}
|
||||
if (context.widget is W) _widget = context.widget as W;
|
||||
}
|
||||
|
||||
// The widget associated with this model. This is useful for accessing the
|
||||
// parameters of the widget, for example.
|
||||
W? _widget;
|
||||
W? get widget => _widget;
|
||||
|
||||
// Dispose methods
|
||||
// Whether to dispose this model when the corresponding widget is
|
||||
// disposed. By default this is true for pages and false for components,
|
||||
// as page/component models handle the disposal of their children.
|
||||
bool disposeOnWidgetDisposal = true;
|
||||
void dispose();
|
||||
void maybeDispose() {
|
||||
if (disposeOnWidgetDisposal) {
|
||||
dispose();
|
||||
}
|
||||
// Remove reference to widget for garbage collection purposes.
|
||||
_widget = null;
|
||||
}
|
||||
|
||||
// Whether to update the containing page / component on updates.
|
||||
bool updateOnChange = false;
|
||||
// Function to call when the model receives an update.
|
||||
VoidCallback _updateCallback = () {};
|
||||
void onUpdate() => updateOnChange ? _updateCallback() : () {};
|
||||
FlutterFlowModel setOnUpdate({
|
||||
bool updateOnChange = false,
|
||||
required VoidCallback onUpdate,
|
||||
}) =>
|
||||
this
|
||||
.._updateCallback = onUpdate
|
||||
..updateOnChange = updateOnChange;
|
||||
// Update the containing page when this model received an update.
|
||||
void updatePage(VoidCallback callback) {
|
||||
callback();
|
||||
_updateCallback();
|
||||
}
|
||||
}
|
||||
|
||||
class FlutterFlowDynamicModels<T extends FlutterFlowModel> {
|
||||
FlutterFlowDynamicModels(this.defaultBuilder);
|
||||
|
||||
final T Function() defaultBuilder;
|
||||
final Map<String, T> _childrenModels = {};
|
||||
final Map<String, int> _childrenIndexes = {};
|
||||
Set<String>? _activeKeys;
|
||||
|
||||
T getModel(String uniqueKey, int index) {
|
||||
_updateActiveKeys(uniqueKey);
|
||||
_childrenIndexes[uniqueKey] = index;
|
||||
return _childrenModels[uniqueKey] ??= defaultBuilder();
|
||||
}
|
||||
|
||||
List<S> getValues<S>(S? Function(T) getValue) {
|
||||
return _childrenIndexes.entries
|
||||
// Sort keys by index.
|
||||
.sorted((a, b) => a.value.compareTo(b.value))
|
||||
.where((e) => _childrenModels[e.key] != null)
|
||||
// Map each model to the desired value and return as list. In order
|
||||
// to preserve index order, rather than removing null values we provide
|
||||
// default values (for types with reasonable defaults).
|
||||
.map((e) => getValue(_childrenModels[e.key]!) ?? _getDefaultValue<S>()!)
|
||||
.toList();
|
||||
}
|
||||
|
||||
S? getValueAtIndex<S>(int index, S? Function(T) getValue) {
|
||||
final uniqueKey =
|
||||
_childrenIndexes.entries.firstWhereOrNull((e) => e.value == index)?.key;
|
||||
return getValueForKey(uniqueKey, getValue);
|
||||
}
|
||||
|
||||
S? getValueForKey<S>(String? uniqueKey, S? Function(T) getValue) {
|
||||
final model = _childrenModels[uniqueKey];
|
||||
return model != null ? getValue(model) : null;
|
||||
}
|
||||
|
||||
void dispose() => _childrenModels.values.forEach((model) => model.dispose());
|
||||
|
||||
void _updateActiveKeys(String uniqueKey) {
|
||||
final shouldResetActiveKeys = _activeKeys == null;
|
||||
_activeKeys ??= {};
|
||||
_activeKeys!.add(uniqueKey);
|
||||
|
||||
if (shouldResetActiveKeys) {
|
||||
// Add a post-frame callback to remove and dispose of unused models after
|
||||
// we're done building, then reset `_activeKeys` to null so we know to do
|
||||
// this again next build.
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_childrenIndexes.removeWhere((k, _) => !_activeKeys!.contains(k));
|
||||
_childrenModels.keys
|
||||
.toSet()
|
||||
.difference(_activeKeys!)
|
||||
// Remove and dispose of unused models since they are not being used
|
||||
// elsewhere and would not otherwise be disposed.
|
||||
.forEach((k) => _childrenModels.remove(k)?.maybeDispose());
|
||||
_activeKeys = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T? _getDefaultValue<T>() {
|
||||
switch (T) {
|
||||
case int:
|
||||
return 0 as T;
|
||||
case double:
|
||||
return 0.0 as T;
|
||||
case String:
|
||||
return '' as T;
|
||||
case bool:
|
||||
return false as T;
|
||||
default:
|
||||
return null as T;
|
||||
}
|
||||
}
|
||||
|
||||
extension TextValidationExtensions on String? Function(BuildContext, String?)? {
|
||||
String? Function(String?)? asValidator(BuildContext context) =>
|
||||
this != null ? (val) => this!(context, val) : null;
|
||||
}
|
||||
353
Application Product/Source/source/lib/flutter_flow/flutter_flow_theme.dart
Executable file
353
Application Product/Source/source/lib/flutter_flow/flutter_flow_theme.dart
Executable file
@@ -0,0 +1,353 @@
|
||||
// ignore_for_file: overridden_fields, annotate_overrides
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
const kThemeModeKey = '__theme_mode__';
|
||||
SharedPreferences? _prefs;
|
||||
|
||||
abstract class FlutterFlowTheme {
|
||||
static Future initialize() async =>
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
static ThemeMode get themeMode {
|
||||
final darkMode = _prefs?.getBool(kThemeModeKey);
|
||||
return darkMode == null
|
||||
? ThemeMode.system
|
||||
: darkMode
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light;
|
||||
}
|
||||
|
||||
static void saveThemeMode(ThemeMode mode) => mode == ThemeMode.system
|
||||
? _prefs?.remove(kThemeModeKey)
|
||||
: _prefs?.setBool(kThemeModeKey, mode == ThemeMode.dark);
|
||||
|
||||
static FlutterFlowTheme of(BuildContext context) {
|
||||
return Theme.of(context).brightness == Brightness.dark
|
||||
? DarkModeTheme()
|
||||
: LightModeTheme();
|
||||
}
|
||||
|
||||
@Deprecated('Use primary instead')
|
||||
Color get primaryColor => primary;
|
||||
@Deprecated('Use secondary instead')
|
||||
Color get secondaryColor => secondary;
|
||||
@Deprecated('Use tertiary instead')
|
||||
Color get tertiaryColor => tertiary;
|
||||
|
||||
late Color primary;
|
||||
late Color secondary;
|
||||
late Color tertiary;
|
||||
late Color alternate;
|
||||
late Color primaryText;
|
||||
late Color secondaryText;
|
||||
late Color primaryBackground;
|
||||
late Color secondaryBackground;
|
||||
late Color accent1;
|
||||
late Color accent2;
|
||||
late Color accent3;
|
||||
late Color accent4;
|
||||
late Color success;
|
||||
late Color warning;
|
||||
late Color error;
|
||||
late Color info;
|
||||
|
||||
@Deprecated('Use displaySmallFamily instead')
|
||||
String get title1Family => displaySmallFamily;
|
||||
@Deprecated('Use displaySmall instead')
|
||||
TextStyle get title1 => typography.displaySmall;
|
||||
@Deprecated('Use headlineMediumFamily instead')
|
||||
String get title2Family => typography.headlineMediumFamily;
|
||||
@Deprecated('Use headlineMedium instead')
|
||||
TextStyle get title2 => typography.headlineMedium;
|
||||
@Deprecated('Use headlineSmallFamily instead')
|
||||
String get title3Family => typography.headlineSmallFamily;
|
||||
@Deprecated('Use headlineSmall instead')
|
||||
TextStyle get title3 => typography.headlineSmall;
|
||||
@Deprecated('Use titleMediumFamily instead')
|
||||
String get subtitle1Family => typography.titleMediumFamily;
|
||||
@Deprecated('Use titleMedium instead')
|
||||
TextStyle get subtitle1 => typography.titleMedium;
|
||||
@Deprecated('Use titleSmallFamily instead')
|
||||
String get subtitle2Family => typography.titleSmallFamily;
|
||||
@Deprecated('Use titleSmall instead')
|
||||
TextStyle get subtitle2 => typography.titleSmall;
|
||||
@Deprecated('Use bodyMediumFamily instead')
|
||||
String get bodyText1Family => typography.bodyMediumFamily;
|
||||
@Deprecated('Use bodyMedium instead')
|
||||
TextStyle get bodyText1 => typography.bodyMedium;
|
||||
@Deprecated('Use bodySmallFamily instead')
|
||||
String get bodyText2Family => typography.bodySmallFamily;
|
||||
@Deprecated('Use bodySmall instead')
|
||||
TextStyle get bodyText2 => typography.bodySmall;
|
||||
|
||||
String get displayLargeFamily => typography.displayLargeFamily;
|
||||
TextStyle get displayLarge => typography.displayLarge;
|
||||
String get displayMediumFamily => typography.displayMediumFamily;
|
||||
TextStyle get displayMedium => typography.displayMedium;
|
||||
String get displaySmallFamily => typography.displaySmallFamily;
|
||||
TextStyle get displaySmall => typography.displaySmall;
|
||||
String get headlineLargeFamily => typography.headlineLargeFamily;
|
||||
TextStyle get headlineLarge => typography.headlineLarge;
|
||||
String get headlineMediumFamily => typography.headlineMediumFamily;
|
||||
TextStyle get headlineMedium => typography.headlineMedium;
|
||||
String get headlineSmallFamily => typography.headlineSmallFamily;
|
||||
TextStyle get headlineSmall => typography.headlineSmall;
|
||||
String get titleLargeFamily => typography.titleLargeFamily;
|
||||
TextStyle get titleLarge => typography.titleLarge;
|
||||
String get titleMediumFamily => typography.titleMediumFamily;
|
||||
TextStyle get titleMedium => typography.titleMedium;
|
||||
String get titleSmallFamily => typography.titleSmallFamily;
|
||||
TextStyle get titleSmall => typography.titleSmall;
|
||||
String get labelLargeFamily => typography.labelLargeFamily;
|
||||
TextStyle get labelLarge => typography.labelLarge;
|
||||
String get labelMediumFamily => typography.labelMediumFamily;
|
||||
TextStyle get labelMedium => typography.labelMedium;
|
||||
String get labelSmallFamily => typography.labelSmallFamily;
|
||||
TextStyle get labelSmall => typography.labelSmall;
|
||||
String get bodyLargeFamily => typography.bodyLargeFamily;
|
||||
TextStyle get bodyLarge => typography.bodyLarge;
|
||||
String get bodyMediumFamily => typography.bodyMediumFamily;
|
||||
TextStyle get bodyMedium => typography.bodyMedium;
|
||||
String get bodySmallFamily => typography.bodySmallFamily;
|
||||
TextStyle get bodySmall => typography.bodySmall;
|
||||
|
||||
Typography get typography => ThemeTypography(this);
|
||||
}
|
||||
|
||||
class LightModeTheme extends FlutterFlowTheme {
|
||||
@Deprecated('Use primary instead')
|
||||
Color get primaryColor => primary;
|
||||
@Deprecated('Use secondary instead')
|
||||
Color get secondaryColor => secondary;
|
||||
@Deprecated('Use tertiary instead')
|
||||
Color get tertiaryColor => tertiary;
|
||||
|
||||
late Color primary = const Color(0xFF4B39EF);
|
||||
late Color secondary = const Color(0xFF39D2C0);
|
||||
late Color tertiary = const Color(0xFFEE8B60);
|
||||
late Color alternate = const Color(0xFFE0E3E7);
|
||||
late Color primaryText = const Color(0xFF14181B);
|
||||
late Color secondaryText = const Color(0xFF57636C);
|
||||
late Color primaryBackground = const Color(0xFFF1F4F8);
|
||||
late Color secondaryBackground = const Color(0xFFFFFFFF);
|
||||
late Color accent1 = const Color(0x4C4B39EF);
|
||||
late Color accent2 = const Color(0x4D39D2C0);
|
||||
late Color accent3 = const Color(0x4DEE8B60);
|
||||
late Color accent4 = const Color(0xCCFFFFFF);
|
||||
late Color success = const Color(0xFF249689);
|
||||
late Color warning = const Color(0xFFF9CF58);
|
||||
late Color error = const Color(0xFFFF5963);
|
||||
late Color info = const Color(0xFFFFFFFF);
|
||||
}
|
||||
|
||||
abstract class Typography {
|
||||
String get displayLargeFamily;
|
||||
TextStyle get displayLarge;
|
||||
String get displayMediumFamily;
|
||||
TextStyle get displayMedium;
|
||||
String get displaySmallFamily;
|
||||
TextStyle get displaySmall;
|
||||
String get headlineLargeFamily;
|
||||
TextStyle get headlineLarge;
|
||||
String get headlineMediumFamily;
|
||||
TextStyle get headlineMedium;
|
||||
String get headlineSmallFamily;
|
||||
TextStyle get headlineSmall;
|
||||
String get titleLargeFamily;
|
||||
TextStyle get titleLarge;
|
||||
String get titleMediumFamily;
|
||||
TextStyle get titleMedium;
|
||||
String get titleSmallFamily;
|
||||
TextStyle get titleSmall;
|
||||
String get labelLargeFamily;
|
||||
TextStyle get labelLarge;
|
||||
String get labelMediumFamily;
|
||||
TextStyle get labelMedium;
|
||||
String get labelSmallFamily;
|
||||
TextStyle get labelSmall;
|
||||
String get bodyLargeFamily;
|
||||
TextStyle get bodyLarge;
|
||||
String get bodyMediumFamily;
|
||||
TextStyle get bodyMedium;
|
||||
String get bodySmallFamily;
|
||||
TextStyle get bodySmall;
|
||||
}
|
||||
|
||||
class ThemeTypography extends Typography {
|
||||
ThemeTypography(this.theme);
|
||||
|
||||
final FlutterFlowTheme theme;
|
||||
|
||||
String get displayLargeFamily => 'Inter Tight';
|
||||
TextStyle get displayLarge => GoogleFonts.getFont(
|
||||
'Inter Tight',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 64.0,
|
||||
);
|
||||
String get displayMediumFamily => 'Inter Tight';
|
||||
TextStyle get displayMedium => GoogleFonts.getFont(
|
||||
'Inter Tight',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 44.0,
|
||||
);
|
||||
String get displaySmallFamily => 'Inter Tight';
|
||||
TextStyle get displaySmall => GoogleFonts.getFont(
|
||||
'Inter Tight',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 36.0,
|
||||
);
|
||||
String get headlineLargeFamily => 'Inter Tight';
|
||||
TextStyle get headlineLarge => GoogleFonts.getFont(
|
||||
'Inter Tight',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 32.0,
|
||||
);
|
||||
String get headlineMediumFamily => 'Inter Tight';
|
||||
TextStyle get headlineMedium => GoogleFonts.getFont(
|
||||
'Inter Tight',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 28.0,
|
||||
);
|
||||
String get headlineSmallFamily => 'Inter Tight';
|
||||
TextStyle get headlineSmall => GoogleFonts.getFont(
|
||||
'Inter Tight',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 24.0,
|
||||
);
|
||||
String get titleLargeFamily => 'Inter Tight';
|
||||
TextStyle get titleLarge => GoogleFonts.getFont(
|
||||
'Inter Tight',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 20.0,
|
||||
);
|
||||
String get titleMediumFamily => 'Inter Tight';
|
||||
TextStyle get titleMedium => GoogleFonts.getFont(
|
||||
'Inter Tight',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18.0,
|
||||
);
|
||||
String get titleSmallFamily => 'Inter Tight';
|
||||
TextStyle get titleSmall => GoogleFonts.getFont(
|
||||
'Inter Tight',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
String get labelLargeFamily => 'Inter';
|
||||
TextStyle get labelLarge => GoogleFonts.getFont(
|
||||
'Inter',
|
||||
color: theme.secondaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
String get labelMediumFamily => 'Inter';
|
||||
TextStyle get labelMedium => GoogleFonts.getFont(
|
||||
'Inter',
|
||||
color: theme.secondaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14.0,
|
||||
);
|
||||
String get labelSmallFamily => 'Inter';
|
||||
TextStyle get labelSmall => GoogleFonts.getFont(
|
||||
'Inter',
|
||||
color: theme.secondaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12.0,
|
||||
);
|
||||
String get bodyLargeFamily => 'Inter';
|
||||
TextStyle get bodyLarge => GoogleFonts.getFont(
|
||||
'Inter',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
String get bodyMediumFamily => 'Inter';
|
||||
TextStyle get bodyMedium => GoogleFonts.getFont(
|
||||
'Inter',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14.0,
|
||||
);
|
||||
String get bodySmallFamily => 'Inter';
|
||||
TextStyle get bodySmall => GoogleFonts.getFont(
|
||||
'Inter',
|
||||
color: theme.primaryText,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 12.0,
|
||||
);
|
||||
}
|
||||
|
||||
class DarkModeTheme extends FlutterFlowTheme {
|
||||
@Deprecated('Use primary instead')
|
||||
Color get primaryColor => primary;
|
||||
@Deprecated('Use secondary instead')
|
||||
Color get secondaryColor => secondary;
|
||||
@Deprecated('Use tertiary instead')
|
||||
Color get tertiaryColor => tertiary;
|
||||
|
||||
late Color primary = const Color(0xFF4B39EF);
|
||||
late Color secondary = const Color(0xFF39D2C0);
|
||||
late Color tertiary = const Color(0xFFEE8B60);
|
||||
late Color alternate = const Color(0xFF262D34);
|
||||
late Color primaryText = const Color(0xFFFFFFFF);
|
||||
late Color secondaryText = const Color(0xFF95A1AC);
|
||||
late Color primaryBackground = const Color(0xFF1D2428);
|
||||
late Color secondaryBackground = const Color(0xFF14181B);
|
||||
late Color accent1 = const Color(0x4C4B39EF);
|
||||
late Color accent2 = const Color(0x4D39D2C0);
|
||||
late Color accent3 = const Color(0x4DEE8B60);
|
||||
late Color accent4 = const Color(0xB2262D34);
|
||||
late Color success = const Color(0xFF249689);
|
||||
late Color warning = const Color(0xFFF9CF58);
|
||||
late Color error = const Color(0xFFFF5963);
|
||||
late Color info = const Color(0xFFFFFFFF);
|
||||
}
|
||||
|
||||
extension TextStyleHelper on TextStyle {
|
||||
TextStyle override({
|
||||
String? fontFamily,
|
||||
Color? color,
|
||||
double? fontSize,
|
||||
FontWeight? fontWeight,
|
||||
double? letterSpacing,
|
||||
FontStyle? fontStyle,
|
||||
bool useGoogleFonts = true,
|
||||
TextDecoration? decoration,
|
||||
double? lineHeight,
|
||||
List<Shadow>? shadows,
|
||||
}) =>
|
||||
useGoogleFonts
|
||||
? GoogleFonts.getFont(
|
||||
fontFamily!,
|
||||
color: color ?? this.color,
|
||||
fontSize: fontSize ?? this.fontSize,
|
||||
letterSpacing: letterSpacing ?? this.letterSpacing,
|
||||
fontWeight: fontWeight ?? this.fontWeight,
|
||||
fontStyle: fontStyle ?? this.fontStyle,
|
||||
decoration: decoration,
|
||||
height: lineHeight,
|
||||
shadows: shadows,
|
||||
)
|
||||
: copyWith(
|
||||
fontFamily: fontFamily,
|
||||
color: color,
|
||||
fontSize: fontSize,
|
||||
letterSpacing: letterSpacing,
|
||||
fontWeight: fontWeight,
|
||||
fontStyle: fontStyle,
|
||||
decoration: decoration,
|
||||
height: lineHeight,
|
||||
shadows: shadows,
|
||||
);
|
||||
}
|
||||
427
Application Product/Source/source/lib/flutter_flow/flutter_flow_util.dart
Executable file
427
Application Product/Source/source/lib/flutter_flow/flutter_flow_util.dart
Executable file
@@ -0,0 +1,427 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:from_css_color/from_css_color.dart';
|
||||
import 'dart:math' show pow, pi, sin;
|
||||
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';
|
||||
|
||||
import 'lat_lng.dart';
|
||||
|
||||
export 'lat_lng.dart';
|
||||
export 'place.dart';
|
||||
export 'uploaded_file.dart';
|
||||
export 'flutter_flow_model.dart';
|
||||
export 'dart:math' show min, max;
|
||||
export 'dart:typed_data' show Uint8List;
|
||||
export 'dart:convert' show jsonEncode, jsonDecode;
|
||||
export 'package:intl/intl.dart';
|
||||
export 'package:page_transition/page_transition.dart';
|
||||
export 'nav/nav.dart';
|
||||
|
||||
T valueOrDefault<T>(T? value, T defaultValue) =>
|
||||
(value is String && value.isEmpty) || value == null ? defaultValue : value;
|
||||
|
||||
String dateTimeFormat(String format, DateTime? dateTime, {String? locale}) {
|
||||
if (dateTime == null) {
|
||||
return '';
|
||||
}
|
||||
if (format == 'relative') {
|
||||
return timeago.format(dateTime, locale: locale, allowFromNow: true);
|
||||
}
|
||||
return DateFormat(format, locale).format(dateTime);
|
||||
}
|
||||
|
||||
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:
|
||||
if (currency != null) {
|
||||
formattedValue = NumberFormat('#,##0.00', 'en_US').format(value);
|
||||
} else {
|
||||
formattedValue = NumberFormat.decimalPattern('en_US').format(value);
|
||||
}
|
||||
break;
|
||||
case DecimalType.commaDecimal:
|
||||
if (currency != null) {
|
||||
formattedValue = NumberFormat('#,##0.00', 'es_PA').format(value);
|
||||
} else {
|
||||
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 setDarkModeSetting(BuildContext context, ThemeMode themeMode) =>
|
||||
MyApp.of(context).setThemeMode(themeMode);
|
||||
|
||||
void showSnackbar(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
bool loading = false,
|
||||
int duration = 4,
|
||||
}) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
if (loading)
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.only(end: 10.0),
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: const CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(message),
|
||||
],
|
||||
),
|
||||
duration: Duration(seconds: duration),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
119
Application Product/Source/source/lib/flutter_flow/flutter_flow_web_view.dart
Executable file
119
Application Product/Source/source/lib/flutter_flow/flutter_flow_web_view.dart
Executable file
@@ -0,0 +1,119 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart' hide NavigationDecision;
|
||||
import 'package:webview_flutter_android/webview_flutter_android.dart';
|
||||
import 'package:webviewx_plus/webviewx_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
|
||||
import 'flutter_flow_util.dart';
|
||||
|
||||
class FlutterFlowWebView extends StatefulWidget {
|
||||
const FlutterFlowWebView({
|
||||
Key? key,
|
||||
required this.content,
|
||||
this.width,
|
||||
this.height,
|
||||
this.bypass = false,
|
||||
this.horizontalScroll = false,
|
||||
this.verticalScroll = false,
|
||||
this.html = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final String content;
|
||||
final double? height;
|
||||
final double? width;
|
||||
final bool bypass;
|
||||
final bool horizontalScroll;
|
||||
final bool verticalScroll;
|
||||
final bool html;
|
||||
|
||||
@override
|
||||
_FlutterFlowWebViewState createState() => _FlutterFlowWebViewState();
|
||||
}
|
||||
|
||||
class _FlutterFlowWebViewState extends State<FlutterFlowWebView> {
|
||||
@override
|
||||
Widget build(BuildContext context) => WebViewX(
|
||||
key: webviewKey,
|
||||
width: widget.width ?? MediaQuery.sizeOf(context).width,
|
||||
height: widget.height ?? MediaQuery.sizeOf(context).height,
|
||||
ignoreAllGestures: false,
|
||||
initialContent: widget.content,
|
||||
initialMediaPlaybackPolicy:
|
||||
AutoMediaPlaybackPolicy.requireUserActionForAllMediaTypes,
|
||||
initialSourceType: widget.html
|
||||
? SourceType.html
|
||||
: widget.bypass
|
||||
? SourceType.urlBypass
|
||||
: SourceType.url,
|
||||
javascriptMode: JavascriptMode.unrestricted,
|
||||
onWebViewCreated: (controller) async {
|
||||
if (controller.connector is WebViewController && isAndroid) {
|
||||
final androidController =
|
||||
controller.connector.platform as AndroidWebViewController;
|
||||
await androidController.setOnShowFileSelector(_androidFilePicker);
|
||||
}
|
||||
},
|
||||
navigationDelegate: (request) async {
|
||||
if (isAndroid) {
|
||||
if (request.content.source
|
||||
.startsWith('https://api.whatsapp.com/send?phone')) {
|
||||
String url = request.content.source;
|
||||
|
||||
await launchUrl(
|
||||
Uri.parse(url),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
return NavigationDecision.prevent;
|
||||
}
|
||||
}
|
||||
return NavigationDecision.navigate;
|
||||
},
|
||||
webSpecificParams: const WebSpecificParams(
|
||||
webAllowFullscreenContent: true,
|
||||
),
|
||||
mobileSpecificParams: MobileSpecificParams(
|
||||
debuggingEnabled: false,
|
||||
gestureNavigationEnabled: true,
|
||||
mobileGestureRecognizers: {
|
||||
if (widget.verticalScroll)
|
||||
Factory<VerticalDragGestureRecognizer>(
|
||||
() => VerticalDragGestureRecognizer(),
|
||||
),
|
||||
if (widget.horizontalScroll)
|
||||
Factory<HorizontalDragGestureRecognizer>(
|
||||
() => HorizontalDragGestureRecognizer(),
|
||||
),
|
||||
},
|
||||
androidEnableHybridComposition: true,
|
||||
),
|
||||
);
|
||||
|
||||
Key get webviewKey => Key(
|
||||
[
|
||||
widget.content,
|
||||
widget.width,
|
||||
widget.height,
|
||||
widget.bypass,
|
||||
widget.horizontalScroll,
|
||||
widget.verticalScroll,
|
||||
widget.html,
|
||||
].map((s) => s?.toString() ?? '').join(),
|
||||
);
|
||||
|
||||
Future<List<String>> _androidFilePicker(
|
||||
final FileSelectorParams params,
|
||||
) async {
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
|
||||
if (result != null && result.files.single.path != null) {
|
||||
final file = File(result.files.single.path!);
|
||||
return [file.uri.toString()];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
288
Application Product/Source/source/lib/flutter_flow/flutter_flow_widgets.dart
Executable file
288
Application Product/Source/source/lib/flutter_flow/flutter_flow_widgets.dart
Executable file
@@ -0,0 +1,288 @@
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
|
||||
class FFButtonOptions {
|
||||
const FFButtonOptions({
|
||||
this.textAlign,
|
||||
this.textStyle,
|
||||
this.elevation,
|
||||
this.height,
|
||||
this.width,
|
||||
this.padding,
|
||||
this.color,
|
||||
this.disabledColor,
|
||||
this.disabledTextColor,
|
||||
this.splashColor,
|
||||
this.iconSize,
|
||||
this.iconColor,
|
||||
this.iconPadding,
|
||||
this.borderRadius,
|
||||
this.borderSide,
|
||||
this.hoverColor,
|
||||
this.hoverBorderSide,
|
||||
this.hoverTextColor,
|
||||
this.hoverElevation,
|
||||
this.maxLines,
|
||||
});
|
||||
|
||||
final TextAlign? textAlign;
|
||||
final TextStyle? textStyle;
|
||||
final double? elevation;
|
||||
final double? height;
|
||||
final double? width;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final Color? color;
|
||||
final Color? disabledColor;
|
||||
final Color? disabledTextColor;
|
||||
final int? maxLines;
|
||||
final Color? splashColor;
|
||||
final double? iconSize;
|
||||
final Color? iconColor;
|
||||
final EdgeInsetsGeometry? iconPadding;
|
||||
final BorderRadius? borderRadius;
|
||||
final BorderSide? borderSide;
|
||||
final Color? hoverColor;
|
||||
final BorderSide? hoverBorderSide;
|
||||
final Color? hoverTextColor;
|
||||
final double? hoverElevation;
|
||||
}
|
||||
|
||||
class FFButtonWidget extends StatefulWidget {
|
||||
const FFButtonWidget({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.onPressed,
|
||||
this.icon,
|
||||
this.iconData,
|
||||
required this.options,
|
||||
this.showLoadingIndicator = true,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final Widget? icon;
|
||||
final IconData? iconData;
|
||||
final Function()? onPressed;
|
||||
final FFButtonOptions options;
|
||||
final bool showLoadingIndicator;
|
||||
|
||||
@override
|
||||
State<FFButtonWidget> createState() => _FFButtonWidgetState();
|
||||
}
|
||||
|
||||
class _FFButtonWidgetState extends State<FFButtonWidget> {
|
||||
bool loading = false;
|
||||
|
||||
int get maxLines => widget.options.maxLines ?? 1;
|
||||
String? get text =>
|
||||
widget.options.textStyle?.fontSize == 0 ? null : widget.text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget textWidget = loading
|
||||
? SizedBox(
|
||||
width: widget.options.width == null
|
||||
? _getTextWidth(text, widget.options.textStyle, maxLines)
|
||||
: null,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 23,
|
||||
height: 23,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
widget.options.textStyle?.color ?? Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: AutoSizeText(
|
||||
text ?? '',
|
||||
style:
|
||||
text == null ? null : widget.options.textStyle?.withoutColor(),
|
||||
textAlign: widget.options.textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
|
||||
final onPressed = widget.onPressed != null
|
||||
? (widget.showLoadingIndicator
|
||||
? () async {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setState(() => loading = true);
|
||||
try {
|
||||
await widget.onPressed!();
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
: () => widget.onPressed!())
|
||||
: null;
|
||||
|
||||
ButtonStyle style = ButtonStyle(
|
||||
shape: MaterialStateProperty.resolveWith<OutlinedBorder>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.hovered) &&
|
||||
widget.options.hoverBorderSide != null) {
|
||||
return RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
widget.options.borderRadius ?? BorderRadius.circular(8),
|
||||
side: widget.options.hoverBorderSide!,
|
||||
);
|
||||
}
|
||||
return RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
widget.options.borderRadius ?? BorderRadius.circular(8),
|
||||
side: widget.options.borderSide ?? BorderSide.none,
|
||||
);
|
||||
},
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.disabled) &&
|
||||
widget.options.disabledTextColor != null) {
|
||||
return widget.options.disabledTextColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered) &&
|
||||
widget.options.hoverTextColor != null) {
|
||||
return widget.options.hoverTextColor;
|
||||
}
|
||||
return widget.options.textStyle?.color ?? Colors.white;
|
||||
},
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.disabled) &&
|
||||
widget.options.disabledColor != null) {
|
||||
return widget.options.disabledColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered) &&
|
||||
widget.options.hoverColor != null) {
|
||||
return widget.options.hoverColor;
|
||||
}
|
||||
return widget.options.color;
|
||||
},
|
||||
),
|
||||
overlayColor: MaterialStateProperty.resolveWith<Color?>((states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return widget.options.splashColor;
|
||||
}
|
||||
return widget.options.hoverColor == null ? null : Colors.transparent;
|
||||
}),
|
||||
padding: MaterialStateProperty.all(widget.options.padding ??
|
||||
const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0)),
|
||||
elevation: MaterialStateProperty.resolveWith<double?>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.hovered) &&
|
||||
widget.options.hoverElevation != null) {
|
||||
return widget.options.hoverElevation!;
|
||||
}
|
||||
return widget.options.elevation ?? 2.0;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if ((widget.icon != null || widget.iconData != null) && !loading) {
|
||||
Widget icon = widget.icon ??
|
||||
FaIcon(
|
||||
widget.iconData!,
|
||||
size: widget.options.iconSize,
|
||||
color: widget.options.iconColor,
|
||||
);
|
||||
|
||||
if (text == null) {
|
||||
return Container(
|
||||
height: widget.options.height,
|
||||
width: widget.options.width,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(
|
||||
widget.options.borderSide ?? BorderSide.none,
|
||||
),
|
||||
borderRadius:
|
||||
widget.options.borderRadius ?? BorderRadius.circular(8),
|
||||
),
|
||||
child: IconButton(
|
||||
splashRadius: 1.0,
|
||||
icon: Padding(
|
||||
padding: widget.options.iconPadding ?? EdgeInsets.zero,
|
||||
child: icon,
|
||||
),
|
||||
onPressed: onPressed,
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
height: widget.options.height,
|
||||
width: widget.options.width,
|
||||
child: ElevatedButton.icon(
|
||||
icon: Padding(
|
||||
padding: widget.options.iconPadding ?? EdgeInsets.zero,
|
||||
child: icon,
|
||||
),
|
||||
label: textWidget,
|
||||
onPressed: onPressed,
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: widget.options.height,
|
||||
width: widget.options.width,
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
style: style,
|
||||
child: textWidget,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension _WithoutColorExtension on TextStyle {
|
||||
TextStyle withoutColor() => TextStyle(
|
||||
inherit: inherit,
|
||||
color: null,
|
||||
backgroundColor: backgroundColor,
|
||||
fontSize: fontSize,
|
||||
fontWeight: fontWeight,
|
||||
fontStyle: fontStyle,
|
||||
letterSpacing: letterSpacing,
|
||||
wordSpacing: wordSpacing,
|
||||
textBaseline: textBaseline,
|
||||
height: height,
|
||||
leadingDistribution: leadingDistribution,
|
||||
locale: locale,
|
||||
foreground: foreground,
|
||||
background: background,
|
||||
shadows: shadows,
|
||||
fontFeatures: fontFeatures,
|
||||
decoration: decoration,
|
||||
decorationColor: decorationColor,
|
||||
decorationStyle: decorationStyle,
|
||||
decorationThickness: decorationThickness,
|
||||
debugLabel: debugLabel,
|
||||
fontFamily: fontFamily,
|
||||
fontFamilyFallback: fontFamilyFallback,
|
||||
// The _package field is private so unfortunately we can't set it here,
|
||||
// but it's almost always unset anyway.
|
||||
// package: _package,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
|
||||
// Slightly hacky method of getting the layout width of the provided text.
|
||||
double? _getTextWidth(String? text, TextStyle? style, int maxLines) =>
|
||||
text != null
|
||||
? (TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.ltr,
|
||||
maxLines: maxLines,
|
||||
)..layout())
|
||||
.size
|
||||
.width
|
||||
: null;
|
||||
19
Application Product/Source/source/lib/flutter_flow/lat_lng.dart
Executable file
19
Application Product/Source/source/lib/flutter_flow/lat_lng.dart
Executable file
@@ -0,0 +1,19 @@
|
||||
class LatLng {
|
||||
const LatLng(this.latitude, this.longitude);
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
|
||||
@override
|
||||
String toString() => 'LatLng(lat: $latitude, lng: $longitude)';
|
||||
|
||||
String serialize() => '$latitude,$longitude';
|
||||
|
||||
@override
|
||||
int get hashCode => latitude.hashCode + longitude.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is LatLng &&
|
||||
latitude == other.latitude &&
|
||||
longitude == other.longitude;
|
||||
}
|
||||
293
Application Product/Source/source/lib/flutter_flow/nav/nav.dart
Executable file
293
Application Product/Source/source/lib/flutter_flow/nav/nav.dart
Executable file
@@ -0,0 +1,293 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:page_transition/page_transition.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '/index.dart';
|
||||
import '/main.dart';
|
||||
import '/flutter_flow/flutter_flow_theme.dart';
|
||||
import '/flutter_flow/lat_lng.dart';
|
||||
import '/flutter_flow/place.dart';
|
||||
import '/flutter_flow/flutter_flow_util.dart';
|
||||
import 'serialization_util.dart';
|
||||
|
||||
export 'package:go_router/go_router.dart';
|
||||
export 'serialization_util.dart';
|
||||
|
||||
const kTransitionInfoKey = '__transition_info__';
|
||||
|
||||
class AppStateNotifier extends ChangeNotifier {
|
||||
AppStateNotifier._();
|
||||
|
||||
static AppStateNotifier? _instance;
|
||||
static AppStateNotifier get instance => _instance ??= AppStateNotifier._();
|
||||
|
||||
bool showSplashImage = true;
|
||||
|
||||
void stopShowingSplashImage() {
|
||||
showSplashImage = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
GoRouter createRouter(AppStateNotifier appStateNotifier) => GoRouter(
|
||||
initialLocation: '/',
|
||||
debugLogDiagnostics: true,
|
||||
refreshListenable: appStateNotifier,
|
||||
errorBuilder: (context, state) => appStateNotifier.showSplashImage
|
||||
? Builder(
|
||||
builder: (context) => Container(
|
||||
color: Colors.transparent,
|
||||
child: Image.asset(
|
||||
'assets/images/Settings_(4).png',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: NavBarPage(),
|
||||
routes: [
|
||||
FFRoute(
|
||||
name: '_initialize',
|
||||
path: '/',
|
||||
builder: (context, _) => appStateNotifier.showSplashImage
|
||||
? Builder(
|
||||
builder: (context) => Container(
|
||||
color: Colors.transparent,
|
||||
child: Image.asset(
|
||||
'assets/images/Settings_(4).png',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: NavBarPage(),
|
||||
),
|
||||
FFRoute(
|
||||
name: 'HomePage',
|
||||
path: '/homePage',
|
||||
builder: (context, params) => params.isEmpty
|
||||
? NavBarPage(initialPage: 'HomePage')
|
||||
: HomePageWidget(),
|
||||
),
|
||||
FFRoute(
|
||||
name: 'Analyst',
|
||||
path: '/analyst',
|
||||
builder: (context, params) => params.isEmpty
|
||||
? NavBarPage(initialPage: 'Analyst')
|
||||
: AnalystWidget(),
|
||||
),
|
||||
FFRoute(
|
||||
name: 'Infomation',
|
||||
path: '/infomation',
|
||||
builder: (context, params) => params.isEmpty
|
||||
? NavBarPage(initialPage: 'Infomation')
|
||||
: InfomationWidget(),
|
||||
),
|
||||
FFRoute(
|
||||
name: 'setting',
|
||||
path: '/setting',
|
||||
builder: (context, params) => params.isEmpty
|
||||
? NavBarPage(initialPage: 'setting')
|
||||
: SettingWidget(),
|
||||
),
|
||||
FFRoute(
|
||||
name: 'NEWS',
|
||||
path: '/news',
|
||||
builder: (context, params) =>
|
||||
params.isEmpty ? NavBarPage(initialPage: 'NEWS') : NewsWidget(),
|
||||
)
|
||||
].map((r) => r.toRoute(appStateNotifier)).toList(),
|
||||
);
|
||||
|
||||
extension NavParamExtensions on Map<String, String?> {
|
||||
Map<String, String> get withoutNulls => Map.fromEntries(
|
||||
entries
|
||||
.where((e) => e.value != null)
|
||||
.map((e) => MapEntry(e.key, e.value!)),
|
||||
);
|
||||
}
|
||||
|
||||
extension NavigationExtensions on BuildContext {
|
||||
void safePop() {
|
||||
// If there is only one route on the stack, navigate to the initial
|
||||
// page instead of popping.
|
||||
if (canPop()) {
|
||||
pop();
|
||||
} else {
|
||||
go('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _GoRouterStateExtensions on GoRouterState {
|
||||
Map<String, dynamic> get extraMap =>
|
||||
extra != null ? extra as Map<String, dynamic> : {};
|
||||
Map<String, dynamic> get allParams => <String, dynamic>{}
|
||||
..addAll(pathParameters)
|
||||
..addAll(uri.queryParameters)
|
||||
..addAll(extraMap);
|
||||
TransitionInfo get transitionInfo => extraMap.containsKey(kTransitionInfoKey)
|
||||
? extraMap[kTransitionInfoKey] as TransitionInfo
|
||||
: TransitionInfo.appDefault();
|
||||
}
|
||||
|
||||
class FFParameters {
|
||||
FFParameters(this.state, [this.asyncParams = const {}]);
|
||||
|
||||
final GoRouterState state;
|
||||
final Map<String, Future<dynamic> Function(String)> asyncParams;
|
||||
|
||||
Map<String, dynamic> futureParamValues = {};
|
||||
|
||||
// Parameters are empty if the params map is empty or if the only parameter
|
||||
// present is the special extra parameter reserved for the transition info.
|
||||
bool get isEmpty =>
|
||||
state.allParams.isEmpty ||
|
||||
(state.allParams.length == 1 &&
|
||||
state.extraMap.containsKey(kTransitionInfoKey));
|
||||
bool isAsyncParam(MapEntry<String, dynamic> param) =>
|
||||
asyncParams.containsKey(param.key) && param.value is String;
|
||||
bool get hasFutures => state.allParams.entries.any(isAsyncParam);
|
||||
Future<bool> completeFutures() => Future.wait(
|
||||
state.allParams.entries.where(isAsyncParam).map(
|
||||
(param) async {
|
||||
final doc = await asyncParams[param.key]!(param.value)
|
||||
.onError((_, __) => null);
|
||||
if (doc != null) {
|
||||
futureParamValues[param.key] = doc;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
),
|
||||
).onError((_, __) => [false]).then((v) => v.every((e) => e));
|
||||
|
||||
dynamic getParam<T>(
|
||||
String paramName,
|
||||
ParamType type, {
|
||||
bool isList = false,
|
||||
}) {
|
||||
if (futureParamValues.containsKey(paramName)) {
|
||||
return futureParamValues[paramName];
|
||||
}
|
||||
if (!state.allParams.containsKey(paramName)) {
|
||||
return null;
|
||||
}
|
||||
final param = state.allParams[paramName];
|
||||
// Got parameter from `extras`, so just directly return it.
|
||||
if (param is! String) {
|
||||
return param;
|
||||
}
|
||||
// Return serialized value.
|
||||
return deserializeParam<T>(
|
||||
param,
|
||||
type,
|
||||
isList,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FFRoute {
|
||||
const FFRoute({
|
||||
required this.name,
|
||||
required this.path,
|
||||
required this.builder,
|
||||
this.requireAuth = false,
|
||||
this.asyncParams = const {},
|
||||
this.routes = const [],
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String path;
|
||||
final bool requireAuth;
|
||||
final Map<String, Future<dynamic> Function(String)> asyncParams;
|
||||
final Widget Function(BuildContext, FFParameters) builder;
|
||||
final List<GoRoute> routes;
|
||||
|
||||
GoRoute toRoute(AppStateNotifier appStateNotifier) => GoRoute(
|
||||
name: name,
|
||||
path: path,
|
||||
pageBuilder: (context, state) {
|
||||
fixStatusBarOniOS16AndBelow(context);
|
||||
final ffParams = FFParameters(state, asyncParams);
|
||||
final page = ffParams.hasFutures
|
||||
? FutureBuilder(
|
||||
future: ffParams.completeFutures(),
|
||||
builder: (context, _) => builder(context, ffParams),
|
||||
)
|
||||
: builder(context, ffParams);
|
||||
final child = page;
|
||||
|
||||
final transitionInfo = state.transitionInfo;
|
||||
return transitionInfo.hasTransition
|
||||
? CustomTransitionPage(
|
||||
key: state.pageKey,
|
||||
child: child,
|
||||
transitionDuration: transitionInfo.duration,
|
||||
transitionsBuilder:
|
||||
(context, animation, secondaryAnimation, child) =>
|
||||
PageTransition(
|
||||
type: transitionInfo.transitionType,
|
||||
duration: transitionInfo.duration,
|
||||
reverseDuration: transitionInfo.duration,
|
||||
alignment: transitionInfo.alignment,
|
||||
child: child,
|
||||
).buildTransitions(
|
||||
context,
|
||||
animation,
|
||||
secondaryAnimation,
|
||||
child,
|
||||
),
|
||||
)
|
||||
: MaterialPage(key: state.pageKey, child: child);
|
||||
},
|
||||
routes: routes,
|
||||
);
|
||||
}
|
||||
|
||||
class TransitionInfo {
|
||||
const TransitionInfo({
|
||||
required this.hasTransition,
|
||||
this.transitionType = PageTransitionType.fade,
|
||||
this.duration = const Duration(milliseconds: 300),
|
||||
this.alignment,
|
||||
});
|
||||
|
||||
final bool hasTransition;
|
||||
final PageTransitionType transitionType;
|
||||
final Duration duration;
|
||||
final Alignment? alignment;
|
||||
|
||||
static TransitionInfo appDefault() => TransitionInfo(hasTransition: false);
|
||||
}
|
||||
|
||||
class RootPageContext {
|
||||
const RootPageContext(this.isRootPage, [this.errorRoute]);
|
||||
final bool isRootPage;
|
||||
final String? errorRoute;
|
||||
|
||||
static bool isInactiveRootPage(BuildContext context) {
|
||||
final rootPageContext = context.read<RootPageContext?>();
|
||||
final isRootPage = rootPageContext?.isRootPage ?? false;
|
||||
final location = GoRouterState.of(context).uri.toString();
|
||||
return isRootPage &&
|
||||
location != '/' &&
|
||||
location != rootPageContext?.errorRoute;
|
||||
}
|
||||
|
||||
static Widget wrap(Widget child, {String? errorRoute}) => Provider.value(
|
||||
value: RootPageContext(true, errorRoute),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
extension GoRouterLocationExtension on GoRouter {
|
||||
String getCurrentLocation() {
|
||||
final RouteMatch lastMatch = routerDelegate.currentConfiguration.last;
|
||||
final RouteMatchList matchList = lastMatch is ImperativeRouteMatch
|
||||
? lastMatch.matches
|
||||
: routerDelegate.currentConfiguration;
|
||||
return matchList.uri.toString();
|
||||
}
|
||||
}
|
||||
206
Application Product/Source/source/lib/flutter_flow/nav/serialization_util.dart
Executable file
206
Application Product/Source/source/lib/flutter_flow/nav/serialization_util.dart
Executable file
@@ -0,0 +1,206 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:from_css_color/from_css_color.dart';
|
||||
|
||||
import '../../flutter_flow/lat_lng.dart';
|
||||
import '../../flutter_flow/place.dart';
|
||||
import '../../flutter_flow/uploaded_file.dart';
|
||||
|
||||
/// SERIALIZATION HELPERS
|
||||
|
||||
String dateTimeRangeToString(DateTimeRange dateTimeRange) {
|
||||
final startStr = dateTimeRange.start.millisecondsSinceEpoch.toString();
|
||||
final endStr = dateTimeRange.end.millisecondsSinceEpoch.toString();
|
||||
return '$startStr|$endStr';
|
||||
}
|
||||
|
||||
String placeToString(FFPlace place) => jsonEncode({
|
||||
'latLng': place.latLng.serialize(),
|
||||
'name': place.name,
|
||||
'address': place.address,
|
||||
'city': place.city,
|
||||
'state': place.state,
|
||||
'country': place.country,
|
||||
'zipCode': place.zipCode,
|
||||
});
|
||||
|
||||
String uploadedFileToString(FFUploadedFile uploadedFile) =>
|
||||
uploadedFile.serialize();
|
||||
|
||||
String? serializeParam(
|
||||
dynamic param,
|
||||
ParamType paramType, {
|
||||
bool isList = false,
|
||||
}) {
|
||||
try {
|
||||
if (param == null) {
|
||||
return null;
|
||||
}
|
||||
if (isList) {
|
||||
final serializedValues = (param as Iterable)
|
||||
.map((p) => serializeParam(p, paramType, isList: false))
|
||||
.where((p) => p != null)
|
||||
.map((p) => p!)
|
||||
.toList();
|
||||
return json.encode(serializedValues);
|
||||
}
|
||||
String? data;
|
||||
switch (paramType) {
|
||||
case ParamType.int:
|
||||
data = param.toString();
|
||||
case ParamType.double:
|
||||
data = param.toString();
|
||||
case ParamType.String:
|
||||
data = param;
|
||||
case ParamType.bool:
|
||||
data = param ? 'true' : 'false';
|
||||
case ParamType.DateTime:
|
||||
data = (param as DateTime).millisecondsSinceEpoch.toString();
|
||||
case ParamType.DateTimeRange:
|
||||
data = dateTimeRangeToString(param as DateTimeRange);
|
||||
case ParamType.LatLng:
|
||||
data = (param as LatLng).serialize();
|
||||
case ParamType.Color:
|
||||
data = (param as Color).toCssString();
|
||||
case ParamType.FFPlace:
|
||||
data = placeToString(param as FFPlace);
|
||||
case ParamType.FFUploadedFile:
|
||||
data = uploadedFileToString(param as FFUploadedFile);
|
||||
case ParamType.JSON:
|
||||
data = json.encode(param);
|
||||
|
||||
default:
|
||||
data = null;
|
||||
}
|
||||
return data;
|
||||
} catch (e) {
|
||||
print('Error serializing parameter: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// END SERIALIZATION HELPERS
|
||||
|
||||
/// DESERIALIZATION HELPERS
|
||||
|
||||
DateTimeRange? dateTimeRangeFromString(String dateTimeRangeStr) {
|
||||
final pieces = dateTimeRangeStr.split('|');
|
||||
if (pieces.length != 2) {
|
||||
return null;
|
||||
}
|
||||
return DateTimeRange(
|
||||
start: DateTime.fromMillisecondsSinceEpoch(int.parse(pieces.first)),
|
||||
end: DateTime.fromMillisecondsSinceEpoch(int.parse(pieces.last)),
|
||||
);
|
||||
}
|
||||
|
||||
LatLng? latLngFromString(String? latLngStr) {
|
||||
final pieces = latLngStr?.split(',');
|
||||
if (pieces == null || pieces.length != 2) {
|
||||
return null;
|
||||
}
|
||||
return LatLng(
|
||||
double.parse(pieces.first.trim()),
|
||||
double.parse(pieces.last.trim()),
|
||||
);
|
||||
}
|
||||
|
||||
FFPlace placeFromString(String placeStr) {
|
||||
final serializedData = jsonDecode(placeStr) as Map<String, dynamic>;
|
||||
final data = {
|
||||
'latLng': serializedData.containsKey('latLng')
|
||||
? latLngFromString(serializedData['latLng'] as String)
|
||||
: const LatLng(0.0, 0.0),
|
||||
'name': serializedData['name'] ?? '',
|
||||
'address': serializedData['address'] ?? '',
|
||||
'city': serializedData['city'] ?? '',
|
||||
'state': serializedData['state'] ?? '',
|
||||
'country': serializedData['country'] ?? '',
|
||||
'zipCode': serializedData['zipCode'] ?? '',
|
||||
};
|
||||
return FFPlace(
|
||||
latLng: data['latLng'] as LatLng,
|
||||
name: data['name'] as String,
|
||||
address: data['address'] as String,
|
||||
city: data['city'] as String,
|
||||
state: data['state'] as String,
|
||||
country: data['country'] as String,
|
||||
zipCode: data['zipCode'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
FFUploadedFile uploadedFileFromString(String uploadedFileStr) =>
|
||||
FFUploadedFile.deserialize(uploadedFileStr);
|
||||
|
||||
enum ParamType {
|
||||
int,
|
||||
double,
|
||||
String,
|
||||
bool,
|
||||
DateTime,
|
||||
DateTimeRange,
|
||||
LatLng,
|
||||
Color,
|
||||
FFPlace,
|
||||
FFUploadedFile,
|
||||
JSON,
|
||||
}
|
||||
|
||||
dynamic deserializeParam<T>(
|
||||
String? param,
|
||||
ParamType paramType,
|
||||
bool isList,
|
||||
) {
|
||||
try {
|
||||
if (param == null) {
|
||||
return null;
|
||||
}
|
||||
if (isList) {
|
||||
final paramValues = json.decode(param);
|
||||
if (paramValues is! Iterable || paramValues.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return paramValues
|
||||
.where((p) => p is String)
|
||||
.map((p) => p as String)
|
||||
.map((p) => deserializeParam<T>(p, paramType, false))
|
||||
.where((p) => p != null)
|
||||
.map((p) => p! as T)
|
||||
.toList();
|
||||
}
|
||||
switch (paramType) {
|
||||
case ParamType.int:
|
||||
return int.tryParse(param);
|
||||
case ParamType.double:
|
||||
return double.tryParse(param);
|
||||
case ParamType.String:
|
||||
return param;
|
||||
case ParamType.bool:
|
||||
return param == 'true';
|
||||
case ParamType.DateTime:
|
||||
final milliseconds = int.tryParse(param);
|
||||
return milliseconds != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(milliseconds)
|
||||
: null;
|
||||
case ParamType.DateTimeRange:
|
||||
return dateTimeRangeFromString(param);
|
||||
case ParamType.LatLng:
|
||||
return latLngFromString(param);
|
||||
case ParamType.Color:
|
||||
return fromCssColor(param);
|
||||
case ParamType.FFPlace:
|
||||
return placeFromString(param);
|
||||
case ParamType.FFUploadedFile:
|
||||
return uploadedFileFromString(param);
|
||||
case ParamType.JSON:
|
||||
return json.decode(param);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error deserializing parameter: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
46
Application Product/Source/source/lib/flutter_flow/place.dart
Executable file
46
Application Product/Source/source/lib/flutter_flow/place.dart
Executable file
@@ -0,0 +1,46 @@
|
||||
import 'lat_lng.dart';
|
||||
|
||||
class FFPlace {
|
||||
const FFPlace({
|
||||
this.latLng = const LatLng(0.0, 0.0),
|
||||
this.name = '',
|
||||
this.address = '',
|
||||
this.city = '',
|
||||
this.state = '',
|
||||
this.country = '',
|
||||
this.zipCode = '',
|
||||
});
|
||||
|
||||
final LatLng latLng;
|
||||
final String name;
|
||||
final String address;
|
||||
final String city;
|
||||
final String state;
|
||||
final String country;
|
||||
final String zipCode;
|
||||
|
||||
@override
|
||||
String toString() => '''FFPlace(
|
||||
latLng: $latLng,
|
||||
name: $name,
|
||||
address: $address,
|
||||
city: $city,
|
||||
state: $state,
|
||||
country: $country,
|
||||
zipCode: $zipCode,
|
||||
)''';
|
||||
|
||||
@override
|
||||
int get hashCode => latLng.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is FFPlace &&
|
||||
latLng == other.latLng &&
|
||||
name == other.name &&
|
||||
address == other.address &&
|
||||
city == other.city &&
|
||||
state == other.state &&
|
||||
country == other.country &&
|
||||
zipCode == other.zipCode;
|
||||
}
|
||||
51
Application Product/Source/source/lib/flutter_flow/random_data_util.dart
Executable file
51
Application Product/Source/source/lib/flutter_flow/random_data_util.dart
Executable file
@@ -0,0 +1,51 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final _random = Random();
|
||||
|
||||
int randomInteger(int min, int max) {
|
||||
return _random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
|
||||
double randomDouble(double min, double max) {
|
||||
return _random.nextDouble() * (max - min) + min;
|
||||
}
|
||||
|
||||
String randomString(
|
||||
int minLength,
|
||||
int maxLength,
|
||||
bool lowercaseAz,
|
||||
bool uppercaseAz,
|
||||
bool digits,
|
||||
) {
|
||||
var chars = '';
|
||||
if (lowercaseAz) {
|
||||
chars += 'abcdefghijklmnopqrstuvwxyz';
|
||||
}
|
||||
if (uppercaseAz) {
|
||||
chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
}
|
||||
if (digits) {
|
||||
chars += '0123456789';
|
||||
}
|
||||
return List.generate(randomInteger(minLength, maxLength),
|
||||
(index) => chars[_random.nextInt(chars.length)]).join();
|
||||
}
|
||||
|
||||
// Random date between 1970 and 2025.
|
||||
DateTime randomDate() {
|
||||
// Random max must be in range 0 < max <= 2^32.
|
||||
// So we have to generate the time in seconds and then convert to milliseconds.
|
||||
return DateTime.fromMillisecondsSinceEpoch(
|
||||
randomInteger(0, 1735689600) * 1000);
|
||||
}
|
||||
|
||||
String randomImageUrl(int width, int height) {
|
||||
return 'https://picsum.photos/seed/${_random.nextInt(1000)}/$width/$height';
|
||||
}
|
||||
|
||||
Color randomColor() {
|
||||
return Color.fromARGB(
|
||||
255, _random.nextInt(255), _random.nextInt(255), _random.nextInt(255));
|
||||
}
|
||||
68
Application Product/Source/source/lib/flutter_flow/uploaded_file.dart
Executable file
68
Application Product/Source/source/lib/flutter_flow/uploaded_file.dart
Executable file
@@ -0,0 +1,68 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data' show Uint8List;
|
||||
|
||||
class FFUploadedFile {
|
||||
const FFUploadedFile({
|
||||
this.name,
|
||||
this.bytes,
|
||||
this.height,
|
||||
this.width,
|
||||
this.blurHash,
|
||||
});
|
||||
|
||||
final String? name;
|
||||
final Uint8List? bytes;
|
||||
final double? height;
|
||||
final double? width;
|
||||
final String? blurHash;
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'FFUploadedFile(name: $name, bytes: ${bytes?.length ?? 0}, height: $height, width: $width, blurHash: $blurHash,)';
|
||||
|
||||
String serialize() => jsonEncode(
|
||||
{
|
||||
'name': name,
|
||||
'bytes': bytes,
|
||||
'height': height,
|
||||
'width': width,
|
||||
'blurHash': blurHash,
|
||||
},
|
||||
);
|
||||
|
||||
static FFUploadedFile deserialize(String val) {
|
||||
final serializedData = jsonDecode(val) as Map<String, dynamic>;
|
||||
final data = {
|
||||
'name': serializedData['name'] ?? '',
|
||||
'bytes': serializedData['bytes'] ?? Uint8List.fromList([]),
|
||||
'height': serializedData['height'],
|
||||
'width': serializedData['width'],
|
||||
'blurHash': serializedData['blurHash'],
|
||||
};
|
||||
return FFUploadedFile(
|
||||
name: data['name'] as String,
|
||||
bytes: Uint8List.fromList(data['bytes'].cast<int>().toList()),
|
||||
height: data['height'] as double?,
|
||||
width: data['width'] as double?,
|
||||
blurHash: data['blurHash'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
name,
|
||||
bytes,
|
||||
height,
|
||||
width,
|
||||
blurHash,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is FFUploadedFile &&
|
||||
name == other.name &&
|
||||
bytes == other.bytes &&
|
||||
height == other.height &&
|
||||
width == other.width &&
|
||||
blurHash == other.blurHash;
|
||||
}
|
||||
Reference in New Issue
Block a user