add hand_signature

This commit is contained in:
otsmr 2025-12-26 18:50:40 +01:00
parent fb66274bf7
commit 83475a9128
17 changed files with 3300 additions and 4 deletions

View file

@ -1,6 +1,7 @@
adaptive_number: ea9178fdd4d82ac45cf0ec966ac870dae661124f
dots_indicator: 508f5883ac79bdbc10254092de3f28f571d261cd
ed25519_edwards: 7353ba759ea9f4646cbf481c2ef949625c8ce4cf
hand_signature: 1beedb164d093643365b0832277c377353c7464f
hashlib: 983cdbd5ee2529b908876b57a7217c09c6bc148a
hashlib_codecs: 2a966c37c3b9b1f5541ae88e99ab34acf3fc968b
introduction_screen: 4a90e557630b28834479ed9c64a9d2d0185d8e48

View file

@ -44,3 +44,6 @@ libsignal_protocol_dart:
git: https://github.com/bcgit/pc-dart.git
x25519:
git: https://github.com/Tougee/curve25519.git
hand_signature:
git: https://github.com/RomanBase/hand_signature.git

View file

@ -0,0 +1,71 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
lints:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
source: hosted
version: "5.1.1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
sdks:
dart: ">=3.8.0-0 <4.0.0"

21
hand_signature/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 RomanBase
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,5 @@
export 'src/signature_control.dart';
export 'src/signature_drawer.dart';
export 'src/signature_paint.dart';
export 'src/signature_painter.dart';
export 'src/signature_view.dart';

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,204 @@
import 'package:flutter/material.dart';
import '../signature.dart';
import 'utils.dart';
/// An abstract base class for custom signature drawing logic.
///
/// Subclasses must implement the [paint] method to define how a signature path is rendered on a canvas.
abstract class HandSignatureDrawer {
/// Creates a [HandSignatureDrawer] instance.
const HandSignatureDrawer();
/// Paints the given [paths] onto the [canvas].
///
/// [canvas] The canvas to draw on.
/// [size] The size of the canvas.
/// [paths] A list of [CubicPath] objects representing the signature to be drawn.
void paint(Canvas canvas, Size size, List<CubicPath> paths);
}
/// A concrete implementation of [HandSignatureDrawer] that draws signature as simple lines.
class LineSignatureDrawer extends HandSignatureDrawer {
/// The color used to paint the lines.
final Color color;
/// The stroke width of the lines.
final double width;
/// Creates a [LineSignatureDrawer] with the specified [width] and [color].
const LineSignatureDrawer({
this.width = 1.0,
this.color = Colors.black,
});
@override
void paint(Canvas canvas, Size size, List<CubicPath> paths) {
final paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..strokeWidth = width;
for (final path in paths) {
if (path.isFilled) {
canvas.drawPath(PathUtil.toLinePath(path.lines), paint);
}
}
}
}
/// A concrete implementation of [HandSignatureDrawer] that draws signatures as arcs,
/// with varying width based on the arc's size property.
class ArcSignatureDrawer extends HandSignatureDrawer {
/// The color used to paint the arcs.
final Color color;
/// The minimal stroke width of the arcs.
final double width;
/// The maximal stroke width of the arcs.
final double maxWidth;
/// Creates an [ArcSignatureDrawer] with the specified [width], [maxWidth], and [color].
const ArcSignatureDrawer({
this.width = 1.0,
this.maxWidth = 10.0,
this.color = Colors.black,
});
@override
void paint(Canvas canvas, Size size, List<CubicPath> paths) {
final paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..strokeWidth = width;
for (final path in paths) {
final arcs = path.toArcs();
for (final arc in arcs) {
paint.strokeWidth = width + (maxWidth - width) * arc.size;
canvas.drawPath(arc.path, paint);
}
}
}
}
/// A concrete implementation of [HandSignatureDrawer] that draws signature as filled Path.
class ShapeSignatureDrawer extends HandSignatureDrawer {
/// The color used to fill the shapes.
final Color color;
/// The base width of the shape.
final double width;
/// The maximum width of the shape.
final double maxWidth;
/// Creates a [ShapeSignatureDrawer] with the specified [width], [maxWidth], and [color].
const ShapeSignatureDrawer({
this.width = 1.0,
this.maxWidth = 10.0,
this.color = Colors.black,
});
@override
void paint(Canvas canvas, Size size, List<CubicPath> paths) {
final paint = Paint()
..color = color
..strokeWidth = 0.0; // Stroke width is handled by the shape path itself
for (final path in paths) {
if (path.isFilled) {
if (path.isDot) {
// If it's a dot, draw a circle
canvas.drawCircle(path.lines[0], path.lines[0].startRadius(width, maxWidth), paint);
} else {
// Otherwise, draw the filled shape path
canvas.drawPath(PathUtil.toShapePath(path.lines, width, maxWidth), paint);
// Draw circles at the start and end of the path for a smoother look
final first = path.lines.first;
final last = path.lines.last;
canvas.drawCircle(first.start, first.startRadius(width, maxWidth), paint);
canvas.drawCircle(last.end, last.endRadius(width, maxWidth), paint);
}
}
}
}
}
/// A [HandSignatureDrawer] that dynamically selects the drawing type based on
/// arguments provided in the [CubicPath]'s setup.
class DynamicSignatureDrawer extends HandSignatureDrawer {
final SignatureDrawType type;
/// The color used to paint the arcs.
final Color color;
/// The minimal stroke width of the arcs.
final double width;
/// The maximal stroke width of the arcs.
final double maxWidth;
const DynamicSignatureDrawer({
this.type = SignatureDrawType.shape,
this.width = 1.0,
this.maxWidth = 10.0,
this.color = Colors.black,
});
@override
void paint(Canvas canvas, Size size, List<CubicPath> paths) {
for (final path in paths) {
// Retrieve drawing parameters from path arguments, with fallbacks
final type = path.setup.args?['type'] ?? this.type.name;
final color = Color(path.setup.args?['color'] ?? this.color.toHex32());
final width = path.setup.args?['width'] ?? this.width;
final maxWidth = path.setup.args?['max_width'] ?? this.maxWidth;
HandSignatureDrawer drawer;
// Select the appropriate drawer based on the 'type' argument
switch (type) {
case 'line':
drawer = LineSignatureDrawer(color: color, width: width);
break;
case 'arc':
drawer = ArcSignatureDrawer(color: color, width: width, maxWidth: maxWidth);
break;
case 'shape':
drawer = ShapeSignatureDrawer(color: color, width: width, maxWidth: maxWidth);
break;
default:
// Default to ShapeSignatureDrawer if type is unknown or not provided
drawer = ShapeSignatureDrawer(color: color, width: width, maxWidth: maxWidth);
}
// Paint the current path using the selected drawer
drawer.paint(canvas, size, [path]);
}
}
}
/// A [HandSignatureDrawer] that combines multiple drawers, allowing for complex
/// drawing effects by applying each drawer in sequence.
class MultiSignatureDrawer extends HandSignatureDrawer {
/// The collection of [HandSignatureDrawer]s to be applied.
final Iterable<HandSignatureDrawer> drawers;
/// Creates a [MultiSignatureDrawer] with the given [drawers].
const MultiSignatureDrawer({required this.drawers});
@override
void paint(Canvas canvas, Size size, List<CubicPath> paths) {
for (final drawer in drawers) {
drawer.paint(canvas, size, paths);
}
}
}

View file

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import '../signature.dart';
/// A [StatefulWidget] that uses [CustomPaint] to render a hand signature.
/// It rebuilds automatically whenever the signature data managed by [HandSignatureControl] changes.
///
/// This widget is typically used internally by [HandSignature] and [HandSignatureView].
class HandSignaturePaint extends StatefulWidget {
/// The controller that manages the signature paths and notifies listeners of changes.
final HandSignatureControl control;
/// The drawer responsible for rendering the signature paths on the canvas.
final HandSignatureDrawer drawer;
/// Optional callback that is invoked when the canvas size changes.
///
/// TODO: This callback should ideally be handled within the State of this widget
/// or by the [HandSignatureControl] itself, rather than being exposed here.
final bool Function(Size size)? onSize;
/// Creates a [HandSignaturePaint] widget.
///
/// [key] Controls how one widget replaces another widget in the tree.
/// [control] The [HandSignatureControl] instance that provides the signature data.
/// [drawer] The [HandSignatureDrawer] instance that defines how the signature is painted.
/// [onSize] An optional callback for canvas size changes.
const HandSignaturePaint({
Key? key,
required this.control,
required this.drawer,
this.onSize,
}) : super(key: key);
@override
_HandSignaturePaintState createState() => _HandSignaturePaintState();
}
/// The state class for [HandSignaturePaint].
///
/// This state subscribes to the [HandSignatureControl] to listen for changes
/// in signature data and triggers a rebuild of the widget when updates occur.
class _HandSignaturePaintState extends State<HandSignaturePaint> {
@override
void initState() {
super.initState();
// Add a listener to the control to trigger a rebuild on data changes.
widget.control.addListener(_updateState);
}
/// Callback method to trigger a widget rebuild.
void _updateState() {
setState(() {});
}
@override
void didUpdateWidget(HandSignaturePaint oldWidget) {
super.didUpdateWidget(oldWidget);
// If the control instance changes, update the listener.
if (oldWidget.control != widget.control) {
oldWidget.control.removeListener(_updateState);
widget.control.addListener(_updateState);
}
}
@override
Widget build(BuildContext context) {
// Use CustomPaint to draw the signature using PathSignaturePainter.
return CustomPaint(
painter: PathSignaturePainter(
paths: widget.control.paths,
drawer: widget.drawer,
onSize: widget.onSize,
),
);
}
@override
void dispose() {
// Remove the listener when the widget is disposed to prevent memory leaks.
widget.control.removeListener(_updateState);
super.dispose();
}
}

View file

@ -0,0 +1,150 @@
import 'package:flutter/material.dart';
import '../signature.dart';
/// Defines the different types of drawing styles for a signature path.
enum SignatureDrawType {
/// Draws the signature as a simple line with a constant stroke width.
/// This is the most basic and performant drawing style.
line,
/// Draws the signature as a series of small arcs.
/// This style can produce a visually appealing result but might have
/// a higher performance cost due to the large number of individual arcs drawn.
arc,
/// Draws the signature by creating a closed shape for each segment of the line and filling it.
/// This method generally provides a good balance between visual quality and performance,
/// resulting in a smooth, filled signature appearance.
shape,
}
/// A [CustomPainter] responsible for rendering [CubicPath]s onto a canvas.
/// This painter is used internally by the signature drawing widgets.
class PathSignaturePainter extends CustomPainter {
/// The list of [CubicPath]s that need to be painted.
final List<CubicPath> paths;
/// The [HandSignatureDrawer] instance that defines the actual drawing logic.
final HandSignatureDrawer drawer;
/// Optional callback that is invoked when the canvas size changes.
///
/// TODO: This callback should ideally be handled within the widget's state
/// or by the [HandSignatureControl] itself.
final bool Function(Size size)? onSize;
/// Creates a [PathSignaturePainter].
///
/// [paths] The list of signature paths to draw.
/// [drawer] The drawer that will perform the actual painting.
/// [onSize] An optional callback for canvas size changes.
const PathSignaturePainter({
required this.paths,
required this.drawer,
this.onSize,
});
@override
void paint(Canvas canvas, Size size) {
// TODO: This size handling logic should be moved to the widget/state.
if (onSize != null) {
if (onSize!.call(size)) {
return;
}
}
// If there are no paths, nothing to draw.
if (paths.isEmpty) {
return;
}
// Delegate the actual painting to the provided drawer.
drawer.paint(canvas, size, paths);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// Always repaint to ensure the latest signature is displayed.
// A more optimized approach might compare old and new paths.
return true;
}
}
/// A [CustomPainter] used for debugging purposes, specifically to visualize
/// the control points and segments of a signature path.
class DebugSignaturePainterCP extends CustomPainter {
/// The [HandSignatureControl] instance providing the signature data.
final HandSignatureControl control;
/// Whether to draw all control points.
final bool cp;
/// Whether to draw control points related to the start of segments.
final bool cpStart;
/// Whether to draw control points related to the end of segments.
final bool cpEnd;
/// Whether to draw dots at the control points and segment ends.
final bool dot;
/// The color used for drawing the debug elements.
final Color color;
/// Creates a [DebugSignaturePainterCP].
///
/// [control] The signature control providing the data to debug.
/// [cp] Whether to draw all control points.
/// [cpStart] Whether to draw control points at the start of segments.
/// [cpEnd] Whether to draw control points at the end of segments.
/// [dot] Whether to draw dots at the control points and segment ends.
/// [color] The color for the debug drawings.
const DebugSignaturePainterCP({
required this.control,
this.cp = false,
this.cpStart = true,
this.cpEnd = true,
this.dot = true,
this.color = Colors.red,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..strokeWidth = 1.0;
// Iterate through each line segment in the control's paths.
control.lines.forEach((line) {
// Draw lines and dots for start control points if enabled.
if (cpStart) {
canvas.drawLine(line.start, line.cpStart, paint);
if (dot) {
canvas.drawCircle(line.cpStart, 1.0, paint);
canvas.drawCircle(line.start, 1.0, paint);
}
} else if (cp) {
// Draw only the control point dot if cpStart is false but cp is true.
canvas.drawCircle(line.cpStart, 1.0, paint);
}
// Draw lines and dots for end control points if enabled.
if (cpEnd) {
canvas.drawLine(line.end, line.cpEnd, paint);
if (dot) {
canvas.drawCircle(line.cpEnd, 1.0, paint);
}
}
});
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// Always repaint to show the latest debug information.
return true;
}
}

View file

@ -0,0 +1,217 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../signature.dart';
typedef _GestureEvent = Function(Offset position, double pressure);
/// A widget that provides a canvas for drawing hand signatures.
/// It combines [HandSignaturePaint] for rendering and [RawGestureDetector] for input handling,
/// sending gesture events to a [HandSignatureControl].
class HandSignature extends StatelessWidget {
/// The controller that manages the creation and manipulation of signature paths.
final HandSignatureControl control;
/// @Deprecated('This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing type and style.')
/// The type of signature path to draw.
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing type and style.')
final SignatureDrawType type;
/// @Deprecated('This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing color.')
/// The single color used for painting the signature.
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing color.')
final Color color;
/// @Deprecated('This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the minimal stroke width.')
/// The minimal stroke width of the signature path.
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the minimal stroke width.')
final double width;
/// @Deprecated('This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the maximal stroke width.')
/// The maximal stroke width of the signature path.
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the maximal stroke width.')
final double maxWidth;
/// The [HandSignatureDrawer] responsible for rendering the signature.
/// If `null`, a default drawer will be created based on the deprecated `type`, `color`, `width`, and `maxWidth` properties.
final HandSignatureDrawer? drawer;
/// The set of [PointerDeviceKind]s that this widget should recognize.
///
/// For example, to only accept stylus input:
/// ```dart
/// supportedDevices: {
/// PointerDeviceKind.stylus,
/// }
/// ```
/// If `null`, it accepts input from all pointer device types.
final Set<PointerDeviceKind>? supportedDevices;
/// Optional callback function invoked when a new signature path drawing starts (pointer down event).
final VoidCallback? onPointerDown;
/// Optional callback function invoked when a signature path drawing ends (pointer up or cancel event).
final VoidCallback? onPointerUp;
/// Creates a [HandSignature] widget.
///
/// [key] Controls how one widget replaces another widget in the tree.
/// [control] The [HandSignatureControl] instance to manage the signature data.
/// [type] The deprecated drawing type for the signature.
/// [color] The deprecated color for the signature.
/// [width] The deprecated minimal width for the signature.
/// [maxWidth] The deprecated maximal width for the signature.
/// [drawer] The custom drawer to use for rendering the signature.
/// [onPointerDown] Callback for when drawing starts.
/// [onPointerUp] Callback for when drawing ends.
/// [supportedDevices] The set of pointer device types to recognize.
const HandSignature({
Key? key,
required this.control,
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing type and style.')
this.type = SignatureDrawType.shape,
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing color.')
this.color = Colors.black,
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the minimal stroke width.')
this.width = 1.0,
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the maximal stroke width.')
this.maxWidth = 10.0,
this.drawer,
this.onPointerDown,
this.onPointerUp,
this.supportedDevices,
}) : super(key: key);
void _startPath(Offset point, double pressure) {
if (!control.hasActivePath) {
onPointerDown?.call();
control.startPath(point, pressure: pressure);
}
}
void _endPath(Offset point, double pressure) {
if (control.hasActivePath) {
control.closePath(pressure: pressure);
onPointerUp?.call();
}
}
@override
Widget build(BuildContext context) {
control.params = SignaturePaintParams(
color: color,
strokeWidth: width,
maxStrokeWidth: maxWidth,
);
return ClipRRect(
child: RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
_SingleGestureRecognizer:
GestureRecognizerFactoryWithHandlers<_SingleGestureRecognizer>(
() => _SingleGestureRecognizer(
debugOwner: this, supportedDevices: supportedDevices),
(instance) {
instance.onStart =
(position, pressure) => _startPath(position, pressure);
instance.onUpdate = (position, pressure) =>
control.alterPath(position, pressure: pressure);
instance.onEnd =
(position, pressure) => _endPath(position, pressure);
},
),
},
child: HandSignaturePaint(
control: control,
drawer: drawer ??
switch (type) {
SignatureDrawType.line =>
LineSignatureDrawer(color: color, width: width),
SignatureDrawType.arc => ArcSignatureDrawer(
color: color, width: width, maxWidth: maxWidth),
SignatureDrawType.shape => ShapeSignatureDrawer(
color: color, width: width, maxWidth: maxWidth),
},
onSize: control.notifyDimension,
),
),
);
}
}
/// A custom [GestureRecognizer] that processes only a single input pointer
/// for signature drawing. It extends [OneSequenceGestureRecognizer] to ensure
/// that only one gesture is recognized at a time.
class _SingleGestureRecognizer extends OneSequenceGestureRecognizer {
@override
String get debugDescription => 'single_gesture_recognizer';
/// Callback function for when a pointer starts interacting with the widget.
_GestureEvent? onStart;
/// Callback function for when a pointer moves while interacting with the widget.
_GestureEvent? onUpdate;
/// Callback function for when a pointer stops interacting with the widget.
_GestureEvent? onEnd;
/// A flag indicating whether a pointer is currently active (down).
bool pointerActive = false;
/// Creates a [_SingleGestureRecognizer].
///
/// [debugOwner] The object that is debugging this recognizer.
/// [supportedDevices] The set of [PointerDeviceKind]s that this recognizer should respond to.
/// If `null`, it defaults to all available pointer device kinds.
_SingleGestureRecognizer({
super.debugOwner,
Set<PointerDeviceKind>? supportedDevices,
}) : super(
supportedDevices:
supportedDevices ?? PointerDeviceKind.values.toSet(),
);
@override
void addAllowedPointer(PointerDownEvent event) {
// Only allow a new pointer if no other pointer is currently active.
if (pointerActive) {
return;
}
// Start tracking the pointer.
startTrackingPointer(event.pointer, event.transform);
}
@override
void handleEvent(PointerEvent event) {
// Handle different types of pointer events.
if (event is PointerMoveEvent) {
// If it's a move event, call the onUpdate callback.
onUpdate?.call(event.localPosition, event.pressure);
} else if (event is PointerDownEvent) {
// If it's a down event, set pointer as active and call onStart.
pointerActive = true;
onStart?.call(event.localPosition, event.pressure);
} else if (event is PointerUpEvent) {
// If it's an up event, set pointer as inactive and call onEnd.
pointerActive = false;
onEnd?.call(event.localPosition, event.pressure);
} else if (event is PointerCancelEvent) {
// If the pointer interaction is cancelled, set pointer as inactive and call onEnd.
pointerActive = false;
onEnd?.call(event.localPosition, event.pressure);
}
}
@override
void didStopTrackingLastPointer(int pointer) {
// No specific action needed when the last pointer stops tracking.
}
}

View file

@ -0,0 +1,634 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../signature.dart';
/// A constant representing 2 * PI (360 degrees in radians).
const pi2 = math.pi * 2.0;
/// Extension methods for [Color] to provide utility functions.
extension ColorEx on Color {
/// Returns the hexadecimal string representation of this color,
/// excluding the alpha component (e.g., '#RRGGBB').
String get hexValue =>
'#${toHex32().toRadixString(16)}'.replaceRange(1, 3, '');
/// {toARGB32} was introduced in Flutter 3.28
int toHex32() {
int floatToInt8(double x) {
return (x * 255.0).round() & 0xff;
}
return floatToInt8(a) << 24 |
floatToInt8(r) << 16 |
floatToInt8(g) << 8 |
floatToInt8(b) << 0;
}
}
/// Extension methods for [Offset] to provide vector and geometric utility functions.
extension OffsetEx on Offset {
/// Calculates the component-wise distance (difference) between this offset and [other].
/// Returns an [Offset] representing (other.dx - this.dx, other.dy - this.dy).
Offset axisDistanceTo(Offset other) => other - this;
/// Calculates the Euclidean distance between this offset and [other].
double distanceTo(Offset other) {
final len = axisDistanceTo(other);
return math.sqrt(len.dx * len.dx + len.dy * len.dy);
}
/// Calculates the angle (in radians) from this offset to [other] relative to the positive x-axis.
double angleTo(Offset other) {
final len = axisDistanceTo(other);
return math.atan2(len.dy, len.dx);
}
/// Calculates the unit vector (direction) from this offset to [other].
/// Returns an [Offset] representing the normalized direction.
/// If the distance is zero, returns an [Offset] of (0,0).
Offset directionTo(Offset other) {
final len = axisDistanceTo(other);
final m = math.sqrt(len.dx * len.dx + len.dy * len.dy);
return Offset(m == 0 ? 0 : (len.dx / m), m == 0 ? 0 : (len.dy / m));
}
/// Rotates this offset by [radians] around the origin (0,0).
/// Returns a new [Offset] representing the rotated point.
Offset rotate(double radians) {
final s = math.sin(radians);
final c = math.cos(radians);
final x = dx * c - dy * s;
final y = dx * s + dy * c;
return Offset(x, y);
}
/// Rotates this offset by [radians] around a specified [center] point.
/// Returns a new [Offset] representing the rotated point.
Offset rotateAround(Offset center, double radians) {
return (this - center).rotate(radians) + center;
}
}
/// Extension methods for [Path] to provide simplified drawing commands.
extension PathEx on Path {
/// Moves the current point of the path to the given [offset].
void start(Offset offset) => moveTo(offset.dx, offset.dy);
/// Adds a cubic Bezier curve segment to the path.
///
/// [cpStart] The first control point.
/// [cpEnd] The second control point.
/// [end] The end point of the curve.
void cubic(Offset cpStart, Offset cpEnd, Offset end) =>
cubicTo(cpStart.dx, cpStart.dy, cpEnd.dx, cpEnd.dy, end.dx, end.dy);
/// Adds a straight line segment from the current point to the given [offset].
void line(Offset offset) => lineTo(offset.dx, offset.dy);
}
/// Extension methods for [Size] to provide utility functions.
extension SizeExt on Size {
/// Scales this size down to fit within [other] size while maintaining aspect ratio.
/// Returns a new [Size] that fits within [other].
Size scaleToFit(Size other) {
final scale = math.min(
other.width / width,
other.height / height,
);
return this * scale;
}
Rect toRect() => Rect.fromLTRB(0.0, 0.0, width, height);
}
/// A utility class providing static methods for common path manipulation and geometric calculations.
/// This includes bounding box calculations, transformations (translate, scale, normalize),
/// and conversions between different path representations.
///
/// TODO: Consider refactoring and cleaning up this class for better organization and clarity.
class PathUtil {
/// Private constructor to prevent direct instantiation of this utility class.
const PathUtil._();
/// Calculates the bounding box (minimum [Rect]) for a list of [Offset] points.
///
/// [data] The list of [Offset] points.
/// [minSize] The minimum width/height for the bounding box. If the calculated
/// size is smaller, it will be expanded to this minimum.
/// [radius] An additional padding to add around the calculated bounds.
/// Returns a [Rect] representing the bounding box.
/// Calculates the bounding box (minimum [Rect]) for a list of [Offset] points.
///
/// [data] The list of [Offset] points.
/// [minSize] The minimum width/height for the bounding box. If the calculated
/// size is smaller, it will be expanded to this minimum.
/// [radius] An additional padding to add around the calculated bounds.
/// Returns a [Rect] representing the bounding box.
static Rect bounds(List<Offset> data,
{double minSize = 2.0, double radius = 0.0}) {
double left = data[0].dx;
double top = data[0].dy;
double right = data[0].dx;
double bottom = data[0].dy;
for (final point in data) {
final x = point.dx;
final y = point.dy;
if (x < left) {
left = x;
} else if (x > right) {
right = x;
}
if (y < top) {
top = y;
} else if (y > bottom) {
bottom = y;
}
}
final hSize = right - left;
final vSize = bottom - top;
if (hSize < minSize) {
final dif = (minSize - hSize) * 0.5;
left -= dif;
right += dif;
}
if (vSize < minSize) {
final dif = (minSize - vSize) * 0.5;
top -= dif;
bottom += dif;
}
return Rect.fromLTRB(
left - radius, top - radius, right + radius, bottom + radius);
}
/// Calculates the bounding box (minimum [Rect]) for a list of lists of [Offset] points.
/// This is useful for finding the overall bounds of multiple paths.
///
/// [data] The list of lists of [Offset] points.
/// [minSize] The minimum width/height for the bounding box.
/// [radius] An additional padding to add around the calculated bounds.
/// Returns a [Rect] representing the combined bounding box.
/// Calculates the bounding box (minimum [Rect]) for a list of lists of [Offset] points.
/// This is useful for finding the overall bounds of multiple paths.
///
/// [data] The list of lists of [Offset] points.
/// [minSize] The minimum width/height for the bounding box.
/// [radius] An additional padding to add around the calculated bounds.
/// Returns a [Rect] representing the combined bounding box.
static Rect boundsOf(List<List<Offset>> data,
{double minSize = 2.0, double radius = 0.0}) {
double left = data[0][0].dx;
double top = data[0][0].dy;
double right = data[0][0].dx;
double bottom = data[0][0].dy;
for (final set in data) {
for (final point in set) {
final x = point.dx;
final y = point.dy;
if (x < left) {
left = x;
} else if (x > right) {
right = x;
}
if (y < top) {
top = y;
} else if (y > bottom) {
bottom = y;
}
}
}
final hSize = right - left;
final vSize = bottom - top;
if (hSize < minSize) {
final dif = (minSize - hSize) * 0.5;
left -= dif;
right += dif;
}
if (vSize < minSize) {
final dif = (minSize - vSize) * 0.5;
top -= dif;
bottom += dif;
}
return Rect.fromLTRB(
left - radius, top - radius, right + radius, bottom + radius);
}
/// Translates a list of [Offset] points by a given [location] offset.
///
/// [data] The list of points to translate.
/// [location] The offset by which to translate the points.
/// Returns a new list of translated points.
/// Translates a list of [Offset] points by a given [location] offset.
///
/// [data] The list of points to translate.
/// [location] The offset by which to translate the points.
/// Returns a new list of translated points.
static List<T> translate<T extends Offset>(List<T> data, Offset location) {
final output = <T>[];
for (final point in data) {
output.add(point.translate(location.dx, location.dy) as T);
}
return output;
}
/// Translates a list of lists of [Offset] points by a given [location] offset.
///
/// [data] The list of lists of points to translate.
/// [location] The offset by which to translate the points.
/// Returns a new list of lists of translated points.
/// Translates a list of lists of [Offset] points by a given [location] offset.
///
/// [data] The list of lists of points to translate.
/// [location] The offset by which to translate the points.
/// Returns a new list of lists of translated points.
static List<List<T>> translateData<T extends Offset>(
List<List<T>> data, Offset location) {
final output = <List<T>>[];
for (final set in data) {
output.add(translate(set, location));
}
return output;
}
/// Scales a list of [Offset] points by a given [ratio].
///
/// [data] The list of points to scale.
/// [ratio] The scaling factor.
/// Returns a new list of scaled points.
/// Scales a list of [Offset] points by a given [ratio].
///
/// [data] The list of points to scale.
/// [ratio] The scaling factor.
/// Returns a new list of scaled points.
static List<T> scale<T extends Offset>(List<T> data, double ratio) {
final output = <T>[];
for (final point in data) {
output.add(point.scale(ratio, ratio) as T);
}
return output;
}
/// Scales a list of lists of [Offset] points by a given [ratio].
///
/// [data] The list of lists of points to scale.
/// [ratio] The scaling factor.
/// Returns a new list of lists of scaled points.
/// Scales a list of lists of [Offset] points by a given [ratio].
///
/// [data] The list of lists of points to scale.
/// [ratio] The scaling factor.
/// Returns a new list of lists of scaled points.
static List<List<T>> scaleData<T extends Offset>(
List<List<T>> data, double ratio) {
final output = <List<T>>[];
for (final set in data) {
output.add(scale(set, ratio));
}
return output;
}
/// Normalizes a list of [Offset] points to a unit square (0-1 range)
/// based on their bounding box.
///
/// [data] The list of points to normalize.
/// [bound] Optional pre-calculated bounding box. If null, it will be calculated.
/// Returns a new list of normalized points.
/// Normalizes a list of [Offset] points to a unit square (0-1 range)
/// based on their bounding box.
///
/// [data] The list of points to normalize.
/// [bound] Optional pre-calculated bounding box. If null, it will be calculated.
/// Returns a new list of normalized points.
static List<T> normalize<T extends Offset>(List<T> data, {Rect? bound}) {
bound ??= bounds(data);
return scale<T>(
translate<T>(data, -bound.topLeft),
1.0 / math.max(bound.width, bound.height),
);
}
/// Normalizes a list of lists of [Offset] points to a unit square (0-1 range)
/// based on their combined bounding box.
///
/// [data] The list of lists of points to normalize.
/// [bound] Optional pre-calculated combined bounding box. If null, it will be calculated.
/// Returns a new list of lists of normalized points.
/// Normalizes a list of lists of [Offset] points to a unit square (0-1 range)
/// based on their combined bounding box.
///
/// [data] The list of lists of points to normalize.
/// [bound] Optional pre-calculated combined bounding box. If null, it will be calculated.
/// Returns a new list of lists of normalized points.
static List<List<T>> normalizeData<T extends Offset>(List<List<T>> data,
{Rect? bound}) {
bound ??= boundsOf(data);
final ratio = 1.0 / math.max(bound.width, bound.height);
return scaleData<T>(
translateData<T>(data, -bound.topLeft),
ratio,
);
}
/// Fills a given [rect] with the scaled and translated [data] points,
/// ensuring they fit within the rectangle with an optional [border].
///
/// [data] The list of points to fill.
/// [rect] The target rectangle to fill.
/// [radius] An additional radius to consider for bounding box calculation.
/// [bound] Optional pre-calculated bounding box for the data.
/// [border] The border size to apply around the filled content.
/// Returns a new list of transformed points.
/// Fills a given [rect] with the scaled and translated [data] points,
/// ensuring they fit within the rectangle with an optional [border].
///
/// [data] The list of points to fill.
/// [rect] The target rectangle to fill.
/// [radius] An additional radius to consider for bounding box calculation.
/// [bound] Optional pre-calculated bounding box for the data.
/// [border] The border size to apply around the filled content.
/// Returns a new list of transformed points.
static List<T> fill<T extends Offset>(List<T> data, Rect rect,
{double radius = 0.0, Rect? bound, double border = 32.0}) {
bound ??= bounds(data, radius: radius);
border *= 2.0;
final outputSize = Size(rect.width - border, rect.height - border);
final sourceSize = Size(bound.width, bound.height);
Size destinationSize;
final wr = outputSize.width / sourceSize.width;
final hr = outputSize.height / sourceSize.height;
if (wr < hr) {
//scale by width
destinationSize = Size(outputSize.width, sourceSize.height * wr);
} else {
//scale by height
destinationSize = Size(sourceSize.width * hr, outputSize.height);
}
final borderSize = Offset(outputSize.width - destinationSize.width + border,
outputSize.height - destinationSize.height + border) *
0.5;
return translate<T>(
scale<T>(
normalize<T>(data, bound: bound),
math.max(destinationSize.width, destinationSize.height),
),
borderSize,
);
}
/// Fills a given [rect] with the scaled and translated list of lists of [data] points,
/// ensuring they fit within the rectangle with an optional [border].
///
/// [data] The list of lists of points to fill.
/// [rect] The target rectangle to fill.
/// [bound] Optional pre-calculated combined bounding box for the data.
/// [border] The border size to apply around the filled content.
/// Returns a new list of lists of transformed points.
/// Fills a given [rect] with the scaled and translated list of lists of [data] points,
/// ensuring they fit within the rectangle with an optional [border].
///
/// [data] The list of lists of points to fill.
/// [rect] The target rectangle to fill.
/// [bound] Optional pre-calculated combined bounding box for the data.
/// [border] The border size to apply around the filled content.
/// Returns a new list of lists of transformed points.
static List<List<T>> fillData<T extends Offset>(List<List<T>> data, Rect rect,
{Rect? bound, double? border}) {
bound ??= boundsOf(data);
border ??= 4.0;
final outputSize = rect.size;
final sourceSize = bound;
Size destinationSize;
if (outputSize.width / outputSize.height >
sourceSize.width / sourceSize.height) {
destinationSize = Size(
sourceSize.width * outputSize.height / sourceSize.height,
outputSize.height);
} else {
destinationSize = Size(outputSize.width,
sourceSize.height * outputSize.width / sourceSize.width);
}
destinationSize = Size(destinationSize.width - border * 2.0,
destinationSize.height - border * 2.0);
final borderSize = Offset(rect.width - destinationSize.width,
rect.height - destinationSize.height) *
0.5;
return translateData<T>(
scaleData<T>(
normalizeData<T>(data, bound: bound),
math.max(destinationSize.width, destinationSize.height),
),
borderSize);
}
/// Converts a list of [Offset] points into a [Path] object by connecting them with lines.
///
/// [points] The list of points to convert.
/// Returns a [Path] representing the connected points.
/// Converts a list of [Offset] points into a [Path] object by connecting them with lines.
///
/// [points] The list of points to convert.
/// Returns a [Path] representing the connected points.
static Path toPath(List<Offset> points) {
final path = Path();
if (points.isNotEmpty) {
path.moveTo(points[0].dx, points[0].dy);
for (final point in points) {
path.lineTo(point.dx, point.dy);
}
}
return path;
}
/// Converts a list of lists of [Offset] points into a list of [Path] objects.
///
/// [data] The list of lists of points to convert.
/// Returns a list of [Path] objects.
/// Converts a list of lists of [Offset] points into a list of [Path] objects.
///
/// [data] The list of lists of points to convert.
/// Returns a list of [Path] objects.
static List<Path> toPaths(List<List<Offset>> data) {
final paths = <Path>[];
for (final line in data) {
paths.add(toPath(line));
}
return paths;
}
/// Calculates the combined bounding box for a list of [Path] objects.
///
/// [data] The list of [Path] objects.
/// Returns a [Rect] representing the combined bounding box.
/// Calculates the combined bounding box for a list of [Path] objects.
///
/// [data] The list of [Path] objects.
/// Returns a [Rect] representing the combined bounding box.
static Rect pathBounds(List<Path> data) {
Rect init = data[0].getBounds();
double left = init.left;
double top = init.top;
double right = init.right;
double bottom = init.bottom;
for (final path in data) {
final bound = path.getBounds();
left = math.min(left, bound.left);
top = math.min(top, bound.top);
right = math.max(right, bound.right);
bottom = math.max(bottom, bound.bottom);
}
return Rect.fromLTRB(left, top, right, bottom);
}
/// Scales a single [Path] object by a given [ratio].
///
/// [data] The [Path] to scale.
/// [ratio] The scaling factor.
/// Returns a new, scaled [Path].
static Path scalePath(Path data, double ratio) {
final transform = Matrix4.identity();
transform.scale(ratio, ratio);
return data.transform(transform.storage);
}
/// Scales a list of [Path] objects by a given [ratio].
///
/// [data] The list of [Path]s to scale.
/// [ratio] The scaling factor.
/// Returns a new list of scaled [Path]s.
/// Scales a list of [Path] objects by a given [ratio].
///
/// [data] The list of [Path]s to scale.
/// [ratio] The scaling factor.
/// Returns a new list of scaled [Path]s.
static List<Path> scalePaths(List<Path> data, double ratio) {
final output = <Path>[];
for (final path in data) {
output.add(scalePath(path, ratio));
}
return output;
}
/// Translates a list of [Path] objects by a given [location] offset.
///
/// [data] The list of [Path]s to translate.
/// [location] The offset by which to translate the paths.
/// Returns a new list of translated [Path]s.
/// Translates a list of [Path] objects by a given [location] offset.
///
/// [data] The list of [Path]s to translate.
/// [location] The offset by which to translate the paths.
/// Returns a new list of translated [Path]s.
static List<Path> translatePaths(List<Path> data, Offset location) {
final output = <Path>[];
final transform = Matrix4.identity();
transform.translate(location.dx, location.dy);
for (final path in data) {
output.add(path.transform(transform.storage));
}
return output;
}
/// Converts a list of [CubicLine] segments into a closed [Path] representing a filled shape.
/// This is typically used for drawing thick, filled signature lines.
///
/// [lines] The list of [CubicLine] segments.
/// [size] The base stroke width for the shape.
/// [maxSize] The maximum stroke width for the shape.
/// Returns a closed [Path] representing the filled shape.
static Path toShapePath(List<CubicLine> lines, double size, double maxSize) {
assert(lines.isNotEmpty);
if (lines.length == 1) {
final line = lines[0];
if (line.isDot) {
// TODO: Consider returning null or creating a circle path directly for dots.
return Path()
..start(line.start)
..line(line.end);
}
return line.toShape(size, maxSize);
}
final path = Path();
final firstLine = lines.first;
path.start(firstLine.start + firstLine.cpsUp(size, maxSize));
for (int i = 0; i < lines.length; i++) {
final line = lines[i];
final d1 = line.cpsUp(size, maxSize);
final d2 = line.cpeUp(size, maxSize);
path.cubic(line.cpStart + d1, line.cpEnd + d2, line.end + d2);
}
final lastLine = lines.last;
path.line(lastLine.end + lastLine.cpeDown(size, maxSize));
for (int i = lines.length - 1; i > -1; i--) {
final line = lines[i];
final d3 = line.cpeDown(size, maxSize);
final d4 = line.cpsDown(size, maxSize);
path.cubic(line.cpEnd + d3, line.cpStart + d4, line.start + d4);
}
path.close();
return path;
}
/// Converts a list of [CubicLine] segments into a simple [Path] object,
/// connecting them with cubic Bezier curves.
///
/// [lines] The list of [CubicLine] segments.
/// Returns a [Path] representing the connected lines.
/// Converts a list of [CubicLine] segments into a simple [Path] object,
/// connecting them with cubic Bezier curves.
///
/// [lines] The list of [CubicLine] segments.
/// Returns a [Path] representing the connected lines.
/// Converts a list of [CubicLine] segments into a simple [Path] object,
/// connecting them with cubic Bezier curves.
///
/// [lines] The list of [CubicLine] segments.
/// Returns a [Path] representing the connected lines.
static Path toLinePath(List<CubicLine> lines) {
assert(lines.isNotEmpty);
final path = Path()..start(lines[0]);
for (final line in lines) {
path.cubic(line.cpStart, line.cpEnd, line.end);
}
return path;
}
}

View file

@ -0,0 +1,15 @@
name: hand_signature
description: The Signature Pad Widget that allows you to draw smooth signatures. With variety of draw and export settings. And also supports SVG.
homepage: https://github.com/romanbase/hand_signature
version: 3.1.0+2
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter

View file

@ -0,0 +1,133 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hand_signature/signature.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
final control = HandSignatureControl();
// mock curve sequence
control.startPath(OffsetPoint(dx: 0.0, dy: 0.0, timestamp: 1));
control.alterPath(OffsetPoint(dx: 10.0, dy: 10.0, timestamp: 10));
control.alterPath(OffsetPoint(dx: 20.0, dy: 20.0, timestamp: 15));
control.alterPath(OffsetPoint(dx: 30.0, dy: 20.0, timestamp: 20));
control.closePath();
// mock dot sequence
control.startPath(OffsetPoint(dx: 30.0, dy: 30.0, timestamp: 25));
control.closePath();
// json string representing above mock data
final json =
'[[{"x":0.0,"y":0.0,"t":1},{"x":10.0,"y":10.0,"t":10},{"x":20.0,"y":20.0,"t":15},{"x":30.0,"y":20.0,"t":20}],[{"x":30.0,"y":30.0,"t":25}]]';
group('IO', () {
test('points', () async {
final paths = control.paths;
final curve = paths[0];
final dot = paths[1];
expect(paths.length, 2);
expect(curve.points.length, 4);
expect(curve.lines.length, 3);
// velocity of first line should be lower because second line is drawn faster while distance is identical
expect(curve.lines[0].end - curve.lines[0].start,
equals(curve.lines[1].end - curve.lines[1].start));
expect(curve.lines[0].velocity(), lessThan(curve.lines[1].velocity()));
expect(dot.points.length, 1);
expect(dot.isDot, isTrue);
});
test('export', () async {
final paths = control.paths;
final export =
'[${paths.map((e) => '[${e.points.map((e) => '{"x":${e.dx},"y":${e.dy},"t":${e.timestamp}}').join(',')}]').join(',')}]';
final data = jsonDecode(export);
expect(data, isNotNull);
expect((data as List).length, 2);
expect((data[0] as List).length, 4);
expect((data[1] as List).length, 1);
expect(export, equals(json));
});
test('import', () async {
final controlIn = HandSignatureControl();
final data = jsonDecode(json) as Iterable;
data.forEach((element) {
final line = List.of(element);
expect(line.length, greaterThan(0));
//start path with first point
controlIn.startPath(OffsetPoint(
dx: line[0]['x'],
dy: line[0]['y'],
timestamp: line[0]['t'],
));
//skip first point and alter path with rest of points
line.skip(1).forEach((item) {
controlIn.alterPath(OffsetPoint(
dx: item['x'],
dy: item['y'],
timestamp: item['t'],
));
});
//close path
controlIn.closePath();
});
final paths = controlIn.paths;
final curve = paths[0];
final dot = paths[1];
expect(paths.length, 2);
expect(curve.points.length, 4);
expect(curve.lines.length, 3);
// velocity of first line is lower because second line is drawn faster while distance is identical
expect(curve.lines[0].end - curve.lines[0].start,
equals(curve.lines[1].end - curve.lines[1].start));
expect(curve.lines[0].velocity(), lessThan(curve.lines[1].velocity()));
expect(dot.points.length, 1);
expect(dot.isDot, isTrue);
// check equality of individual OffsetPoints of CubePaths
expect(controlIn.equals(control), isTrue);
});
test('map', () async {
final controlMap = HandSignatureControl();
controlMap.import(control.toMap());
// check equality of individual OffsetPoints of CubePaths
expect(controlMap.equals(control), isTrue);
});
test('image', () async {
final controlImage = HandSignatureControl();
controlImage.import(control.toMap());
controlImage.notifyDimension(Size(1280, 720));
final image = await controlImage.toImage();
expect(image, isNotNull);
final data = image!.buffer.asUint8List();
expect(data, isNotNull);
});
});
}

357
lottie/pubspec.lock Normal file
View file

@ -0,0 +1,357 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e"
url: "https://pub.dev"
source: hosted
version: "92.0.0"
analyzer:
dependency: "direct dev"
description:
name: analyzer
sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e"
url: "https://pub.dev"
source: hosted
version: "9.0.0"
archive:
dependency: "direct main"
description:
name: archive
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
url: "https://pub.dev"
source: hosted
version: "4.0.7"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
dart_style:
dependency: "direct dev"
description:
name: dart_style
sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b
url: "https://pub.dev"
source: hosted
version: "3.1.3"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
http:
dependency: "direct main"
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
lints:
dependency: transitive
description:
name: lints
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
url: "https://pub.dev"
source: hosted
version: "6.0.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
package_config:
dependency: transitive
description:
name: package_config
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev"
source: hosted
version: "2.2.0"
path:
dependency: "direct main"
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
posix:
dependency: transitive
description:
name: posix
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
pub_semver:
dependency: "direct dev"
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.7"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math:
dependency: "direct main"
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
watcher:
dependency: transitive
description:
name: watcher
sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249
url: "https://pub.dev"
source: hosted
version: "1.2.0"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
yaml:
dependency: "direct dev"
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.9.0 <4.0.0"
flutter: ">=3.35.0"

View file

@ -445,10 +445,10 @@ packages:
dependency: transitive
description:
name: watcher
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249
url: "https://pub.dev"
source: hosted
version: "1.1.4"
version: "1.2.0"
web:
dependency: transitive
description:

View file

@ -420,10 +420,10 @@ packages:
dependency: transitive
description:
name: watcher
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249
url: "https://pub.dev"
source: hosted
version: "1.1.4"
version: "1.2.0"
web:
dependency: transitive
description:

View file

@ -5,6 +5,8 @@ dependency_overrides:
path: ./dependencies/dots_indicator
ed25519_edwards:
path: ./dependencies/ed25519_edwards
hand_signature:
path: ./dependencies/hand_signature
hashlib:
path: ./dependencies/hashlib
hashlib_codecs: