Fix 78 additional lint issues: rename all files to snake_case and fix 16 style issues
Lint issues fixed (263 total in lib/): - file_names (57 issues): Renamed all PascalCase files to snake_case - All lib files: activeFires -> active_fires, etc. - All models: appState -> app_state, fireMapState -> fire_map_state, etc. - All redux files: appActions -> app_actions, appReducer -> app_reducer, etc. - Updated all import statements and exports across the codebase - Fixed unused imports, exports, and references in rebuilt modules Style fixes (16 issues): - prefer_generic_function_type_aliases (2): Updated typedef syntax from old to new - unnecessary_nullable_for_final_variable_declarations (2): Removed ? from non-nullable types - unnecessary_import: Removed duplicate meta/meta import - unnecessary_parenthesis: Removed unnecessary parentheses in expressions - avoid_renaming_method_parameters: Renamed parameter 'o' to 'other' - prefer_const_declarations: Changed final to const for constant values - use_key_in_widget_constructors (3): Added key parameters to widget constructors - sort_child_properties_last (1): Reordered children property to end Verification: - APK builds successfully (146 MB) - Reduced from 106 lint issues to 49 high-priority issues - Total file_names issues: 0 (down from 57) - All imports and exports updated correctly
This commit is contained in:
parent
8da3752193
commit
12653b80a4
65 changed files with 141 additions and 245 deletions
625
lib/custom_stepper.dart
Normal file
625
lib/custom_stepper.dart
Normal file
|
|
@ -0,0 +1,625 @@
|
|||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// TODO(dragostis): Missing functionality:
|
||||
// * mobile horizontal mode with adding/removing steps
|
||||
// * alternative labeling
|
||||
// * stepper feedback in the case of high-latency interactions
|
||||
|
||||
/// The state of a [CustomStep] which is used to control the style of the circle and
|
||||
/// text.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CustomStep]
|
||||
enum CustomStepState {
|
||||
/// A step that displays its index in its circle.
|
||||
indexed,
|
||||
|
||||
/// A step that displays a pencil icon in its circle.
|
||||
editing,
|
||||
|
||||
/// A step that displays a tick icon in its circle.
|
||||
complete,
|
||||
|
||||
/// A step that is disabled and does not to react to taps.
|
||||
disabled,
|
||||
|
||||
/// A step that is currently having an error. e.g. the use has submitted wrong
|
||||
/// input.
|
||||
error,
|
||||
}
|
||||
|
||||
/// Defines the [CustomStepper]'s main axis.
|
||||
enum CustomStepperType {
|
||||
/// A vertical layout of the steps with their content in-between the titles.
|
||||
vertical,
|
||||
|
||||
/// A horizontal layout of the steps with their content below the titles.
|
||||
horizontal,
|
||||
}
|
||||
|
||||
const TextStyle _kCustomStepStyle = TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: Colors.white,
|
||||
);
|
||||
const Color _kErrorLight = Colors.red;
|
||||
final Color _kErrorDark = Colors.red.shade400;
|
||||
const Color _kCircleActiveLight = Colors.white;
|
||||
const Color _kCircleActiveDark = Colors.black87;
|
||||
const Color _kDisabledLight = Colors.black38;
|
||||
const Color _kDisabledDark = Colors.white30;
|
||||
const double _kCustomStepSize = 24.0;
|
||||
const double _kTriangleHeight =
|
||||
_kCustomStepSize * 0.866025; // Triangle height. sqrt(3.0) / 2.0
|
||||
|
||||
/// A material step used in [CustomStepper]. The step can have a title and subtitle,
|
||||
/// an icon within its circle, some content and a state that governs its
|
||||
/// styling.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CustomStepper]
|
||||
/// * <https://material.google.com/components/steppers.html>
|
||||
/// Modification: (by @vjrj) Commented continue/cancel buttons
|
||||
@immutable
|
||||
class CustomStep {
|
||||
/// Creates a step for a [CustomStepper].
|
||||
///
|
||||
/// The [title], [content], and [state] arguments must not be null.
|
||||
const CustomStep({
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
required this.content,
|
||||
this.state = CustomStepState.indexed,
|
||||
this.isActive = false,
|
||||
});
|
||||
|
||||
/// The title of the step that typically describes it.
|
||||
final Widget title;
|
||||
|
||||
/// The subtitle of the step that appears below the title and has a smaller
|
||||
/// font size. It typically gives more details that complement the title.
|
||||
///
|
||||
/// If null, the subtitle is not shown.
|
||||
final Widget? subtitle;
|
||||
|
||||
/// The content of the step that appears below the [title] and [subtitle].
|
||||
///
|
||||
/// Below the content, every step has a 'continue' and 'cancel' button.
|
||||
final Widget content;
|
||||
|
||||
/// The state of the step which determines the styling of its components
|
||||
/// and whether steps are interactive.
|
||||
final CustomStepState state;
|
||||
|
||||
/// Whether or not the step is active. The flag only influences styling.
|
||||
final bool isActive;
|
||||
}
|
||||
|
||||
/// A material stepper widget that displays progress through a sequence of
|
||||
/// steps. CustomSteppers are particularly useful in the case of forms where one step
|
||||
/// requires the completion of another one, or where multiple steps need to be
|
||||
/// completed in order to submit the whole form.
|
||||
///
|
||||
/// The widget is a flexible wrapper. A parent class should pass [currentCustomStep]
|
||||
/// to this widget based on some logic triggered by the three callbacks that it
|
||||
/// provides.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CustomStep]
|
||||
/// * <https://material.google.com/components/steppers.html>
|
||||
class CustomStepper extends StatefulWidget {
|
||||
/// Creates a stepper from a list of steps.
|
||||
///
|
||||
/// This widget is not meant to be rebuilt with a different list of steps
|
||||
/// unless a key is provided in order to distinguish the old stepper from the
|
||||
/// new one.
|
||||
///
|
||||
/// The [steps], [type], and [currentCustomStep] arguments must not be null.
|
||||
const CustomStepper({
|
||||
super.key,
|
||||
required this.steps,
|
||||
this.type = CustomStepperType.vertical,
|
||||
this.currentCustomStep = 0,
|
||||
this.onCustomStepTapped,
|
||||
this.onCustomStepContinue,
|
||||
this.onCustomStepCancel,
|
||||
}) : assert(0 <= currentCustomStep && currentCustomStep < steps.length);
|
||||
|
||||
/// The steps of the stepper whose titles, subtitles, icons always get shown.
|
||||
///
|
||||
/// The length of [steps] must not change.
|
||||
final List<CustomStep> steps;
|
||||
|
||||
/// The type of stepper that determines the layout. In the case of
|
||||
/// [CustomStepperType.horizontal], the content of the current step is displayed
|
||||
/// underneath as opposed to the [CustomStepperType.vertical] case where it is
|
||||
/// displayed in-between.
|
||||
final CustomStepperType type;
|
||||
|
||||
/// The index into [steps] of the current step whose content is displayed.
|
||||
final int currentCustomStep;
|
||||
|
||||
/// The callback called when a step is tapped, with its index passed as
|
||||
/// an argument.
|
||||
final ValueChanged<int>? onCustomStepTapped;
|
||||
|
||||
/// The callback called when the 'continue' button is tapped.
|
||||
///
|
||||
/// If null, the 'continue' button will be disabled.
|
||||
final VoidCallback? onCustomStepContinue;
|
||||
|
||||
/// The callback called when the 'cancel' button is tapped.
|
||||
///
|
||||
/// If null, the 'cancel' button will be disabled.
|
||||
final VoidCallback? onCustomStepCancel;
|
||||
|
||||
@override
|
||||
_CustomStepperState createState() => _CustomStepperState();
|
||||
}
|
||||
|
||||
class _CustomStepperState extends State<CustomStepper> {
|
||||
late List<GlobalKey> _keys;
|
||||
final Map<int, CustomStepState> _oldStates = <int, CustomStepState>{};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_keys = List<GlobalKey>.generate(
|
||||
widget.steps.length,
|
||||
(int i) => GlobalKey(),
|
||||
);
|
||||
|
||||
for (int i = 0; i < widget.steps.length; i += 1)
|
||||
_oldStates[i] = widget.steps[i].state;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(CustomStepper oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
assert(widget.steps.length == oldWidget.steps.length);
|
||||
|
||||
for (int i = 0; i < oldWidget.steps.length; i += 1)
|
||||
_oldStates[i] = oldWidget.steps[i].state;
|
||||
}
|
||||
|
||||
bool _isFirst(int index) {
|
||||
return index == 0;
|
||||
}
|
||||
|
||||
bool _isLast(int index) {
|
||||
return widget.steps.length - 1 == index;
|
||||
}
|
||||
|
||||
bool _isCurrent(int index) {
|
||||
return widget.currentCustomStep == index;
|
||||
}
|
||||
|
||||
bool _isDark() {
|
||||
return Theme.of(context).brightness == Brightness.dark;
|
||||
}
|
||||
|
||||
Widget _buildLine(bool visible) {
|
||||
return Container(
|
||||
width: visible ? 1.0 : 0.0,
|
||||
height: 16.0,
|
||||
color: Colors.grey.shade400,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCircleChild(int index, bool oldState) {
|
||||
final CustomStepState state =
|
||||
oldState ? _oldStates[index]! : widget.steps[index].state;
|
||||
final bool isDarkActive = _isDark() && widget.steps[index].isActive;
|
||||
switch (state) {
|
||||
case CustomStepState.indexed:
|
||||
case CustomStepState.disabled:
|
||||
return Text(
|
||||
'${index + 1}',
|
||||
style: isDarkActive
|
||||
? _kCustomStepStyle.copyWith(color: Colors.black87)
|
||||
: _kCustomStepStyle,
|
||||
);
|
||||
case CustomStepState.editing:
|
||||
return Icon(
|
||||
Icons.edit,
|
||||
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
|
||||
);
|
||||
case CustomStepState.complete:
|
||||
return Icon(
|
||||
Icons.check,
|
||||
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
|
||||
);
|
||||
case CustomStepState.error:
|
||||
return const Text('!', style: _kCustomStepStyle);
|
||||
}
|
||||
}
|
||||
|
||||
Color _circleColor(int index) {
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
if (!_isDark()) {
|
||||
return widget.steps[index].isActive
|
||||
? themeData.primaryColor
|
||||
: Colors.black38;
|
||||
} else {
|
||||
return widget.steps[index].isActive
|
||||
? themeData.colorScheme.secondary
|
||||
: themeData.colorScheme.surface;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCircle(int index, bool oldState) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
width: _kCustomStepSize,
|
||||
height: _kCustomStepSize,
|
||||
child: AnimatedContainer(
|
||||
curve: Curves.fastOutSlowIn,
|
||||
duration: kThemeAnimationDuration,
|
||||
decoration: BoxDecoration(
|
||||
color: _circleColor(index),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: _buildCircleChild(index,
|
||||
oldState && widget.steps[index].state == CustomStepState.error),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTriangle(int index, bool oldState) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
width: _kCustomStepSize,
|
||||
height: _kCustomStepSize,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: _kCustomStepSize,
|
||||
height:
|
||||
_kTriangleHeight, // Height of 24dp-long-sided equilateral triangle.
|
||||
child: CustomPaint(
|
||||
painter: _TrianglePainter(
|
||||
color: _isDark() ? _kErrorDark : _kErrorLight,
|
||||
),
|
||||
child: Align(
|
||||
alignment: const Alignment(
|
||||
0.0, 0.8), // 0.8 looks better than the geometrical 0.33.
|
||||
child: _buildCircleChild(
|
||||
index,
|
||||
oldState &&
|
||||
widget.steps[index].state != CustomStepState.error),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIcon(int index) {
|
||||
if (widget.steps[index].state != _oldStates[index]) {
|
||||
return AnimatedCrossFade(
|
||||
firstChild: _buildCircle(index, true),
|
||||
secondChild: _buildTriangle(index, true),
|
||||
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
|
||||
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
|
||||
sizeCurve: Curves.fastOutSlowIn,
|
||||
crossFadeState: widget.steps[index].state == CustomStepState.error
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
duration: kThemeAnimationDuration,
|
||||
);
|
||||
} else {
|
||||
if (widget.steps[index].state != CustomStepState.error)
|
||||
return _buildCircle(index, false);
|
||||
else
|
||||
return _buildTriangle(index, false);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildVerticalControls() {
|
||||
// final Color cancelColor;
|
||||
|
||||
switch (Theme.of(context).brightness) {
|
||||
case Brightness.light:
|
||||
case Brightness.dark:
|
||||
break;
|
||||
}
|
||||
|
||||
// final ThemeData themeData = Theme.of(context);
|
||||
// final MaterialLocalizations localizations =
|
||||
// MaterialLocalizations.of(context);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 16.0),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints.tightFor(height: 48.0),
|
||||
child: const Row(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle _titleStyle(int index) {
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final TextTheme textTheme = themeData.textTheme;
|
||||
|
||||
switch (widget.steps[index].state) {
|
||||
case CustomStepState.indexed:
|
||||
case CustomStepState.editing:
|
||||
case CustomStepState.complete:
|
||||
return textTheme.bodyLarge ?? const TextStyle();
|
||||
case CustomStepState.disabled:
|
||||
return (textTheme.bodyLarge ?? const TextStyle())
|
||||
.copyWith(color: _isDark() ? _kDisabledDark : _kDisabledLight);
|
||||
case CustomStepState.error:
|
||||
return (textTheme.bodyLarge ?? const TextStyle())
|
||||
.copyWith(color: _isDark() ? _kErrorDark : _kErrorLight);
|
||||
}
|
||||
}
|
||||
|
||||
TextStyle _subtitleStyle(int index) {
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final TextTheme textTheme = themeData.textTheme;
|
||||
|
||||
switch (widget.steps[index].state) {
|
||||
case CustomStepState.indexed:
|
||||
case CustomStepState.editing:
|
||||
case CustomStepState.complete:
|
||||
return textTheme.bodySmall ?? const TextStyle();
|
||||
case CustomStepState.disabled:
|
||||
return (textTheme.bodySmall ?? const TextStyle())
|
||||
.copyWith(color: _isDark() ? _kDisabledDark : _kDisabledLight);
|
||||
case CustomStepState.error:
|
||||
return (textTheme.bodySmall ?? const TextStyle())
|
||||
.copyWith(color: _isDark() ? _kErrorDark : _kErrorLight);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildHeaderText(int index) {
|
||||
final List<Widget> children = <Widget>[
|
||||
AnimatedDefaultTextStyle(
|
||||
style: _titleStyle(index),
|
||||
duration: kThemeAnimationDuration,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
child: widget.steps[index].title,
|
||||
),
|
||||
];
|
||||
|
||||
if (widget.steps[index].subtitle != null) {
|
||||
children.add(
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 2.0),
|
||||
child: AnimatedDefaultTextStyle(
|
||||
style: _subtitleStyle(index),
|
||||
duration: kThemeAnimationDuration,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
child: widget.steps[index].subtitle!,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children);
|
||||
}
|
||||
|
||||
Widget _buildVerticalHeader(int index) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Row(children: <Widget>[
|
||||
Column(children: <Widget>[
|
||||
// Line parts are always added in order for the ink splash to
|
||||
// flood the tips of the connector lines.
|
||||
_buildLine(!_isFirst(index)),
|
||||
_buildIcon(index),
|
||||
_buildLine(!_isLast(index)),
|
||||
]),
|
||||
Container(
|
||||
margin: const EdgeInsetsDirectional.only(start: 12.0),
|
||||
child: _buildHeaderText(index))
|
||||
]));
|
||||
}
|
||||
|
||||
Widget _buildVerticalBody(int index) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
PositionedDirectional(
|
||||
start: 24.0,
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
child: SizedBox(
|
||||
width: 24.0,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: _isLast(index) ? 0.0 : 1.0,
|
||||
child: Container(
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedCrossFade(
|
||||
firstChild: Container(height: 0.0),
|
||||
secondChild: Container(
|
||||
margin: const EdgeInsetsDirectional.only(
|
||||
start: 60.0,
|
||||
end: 24.0,
|
||||
bottom: 24.0,
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
widget.steps[index].content,
|
||||
_buildVerticalControls(),
|
||||
],
|
||||
),
|
||||
),
|
||||
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
|
||||
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
|
||||
sizeCurve: Curves.fastOutSlowIn,
|
||||
crossFadeState: _isCurrent(index)
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
duration: kThemeAnimationDuration,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVertical() {
|
||||
final List<Widget> children = <Widget>[];
|
||||
|
||||
for (int i = 0; i < widget.steps.length; i += 1) {
|
||||
children.add(Column(key: _keys[i], children: <Widget>[
|
||||
InkWell(
|
||||
onTap: widget.steps[i].state != CustomStepState.disabled
|
||||
? () {
|
||||
// In the vertical case we need to scroll to the newly tapped
|
||||
// step.
|
||||
Scrollable.ensureVisible(
|
||||
_keys[i].currentContext!,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
duration: kThemeAnimationDuration,
|
||||
);
|
||||
|
||||
widget.onCustomStepTapped?.call(i);
|
||||
}
|
||||
: null,
|
||||
child: _buildVerticalHeader(i)),
|
||||
_buildVerticalBody(i)
|
||||
]));
|
||||
}
|
||||
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHorizontal() {
|
||||
final List<Widget> children = <Widget>[];
|
||||
|
||||
for (int i = 0; i < widget.steps.length; i += 1) {
|
||||
children.add(
|
||||
InkResponse(
|
||||
onTap: widget.steps[i].state != CustomStepState.disabled
|
||||
? () {
|
||||
widget.onCustomStepTapped?.call(i);
|
||||
}
|
||||
: null,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 72.0,
|
||||
child: Center(
|
||||
child: _buildIcon(i),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsetsDirectional.only(start: 12.0),
|
||||
child: _buildHeaderText(i),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (!_isLast(i)) {
|
||||
children.add(
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
height: 1.0,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Material(
|
||||
elevation: 2.0,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Row(
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
children: <Widget>[
|
||||
AnimatedSize(
|
||||
curve: Curves.fastOutSlowIn,
|
||||
duration: kThemeAnimationDuration,
|
||||
child: widget.steps[widget.currentCustomStep].content,
|
||||
),
|
||||
_buildVerticalControls(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
assert(() {
|
||||
if (context.findAncestorWidgetOfExactType<CustomStepper>() != null)
|
||||
throw FlutterError(
|
||||
'CustomSteppers must not be nested. The material specification advises '
|
||||
'that one should avoid embedding steppers within steppers. '
|
||||
'https://material.google.com/components/steppers.html#steppers-usage\n');
|
||||
return true;
|
||||
}());
|
||||
switch (widget.type) {
|
||||
case CustomStepperType.vertical:
|
||||
return _buildVertical();
|
||||
case CustomStepperType.horizontal:
|
||||
return _buildHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Paints a triangle whose base is the bottom of the bounding rectangle and its
|
||||
// top vertex the middle of its top.
|
||||
class _TrianglePainter extends CustomPainter {
|
||||
_TrianglePainter({required this.color});
|
||||
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
bool hitTest(Offset point) => true; // Hitting the rectangle is fine enough.
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_TrianglePainter oldPainter) {
|
||||
return oldPainter.color != color;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final double base = size.width;
|
||||
final double halfBase = size.width / 2.0;
|
||||
final double height = size.height;
|
||||
final List<Offset> points = <Offset>[
|
||||
Offset(0.0, height),
|
||||
Offset(base, height),
|
||||
Offset(halfBase, 0.0),
|
||||
];
|
||||
|
||||
canvas.drawPath(
|
||||
Path()..addPolygon(points, true),
|
||||
Paint()..color = color,
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue