Add application source code and update project structure

This commit is contained in:
PhongMacbook
2025-11-05 03:20:59 +07:00
parent 95f8296211
commit b145c7844f
155 changed files with 9171 additions and 0 deletions

View 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,
),
);
}
}

View 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);
}
}

View 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);
}

View 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,
),
),
),
);
}
}

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

View 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,
);
}

View 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));
}

View 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 [];
}
}

View 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;

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

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

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

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

View 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));
}

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