diff --git a/lib/cubit/canvas_cubit.dart b/lib/cubit/canvas_cubit.dart index 4afeaa3c..6e12edbd 100644 --- a/lib/cubit/canvas_cubit.dart +++ b/lib/cubit/canvas_cubit.dart @@ -360,10 +360,33 @@ class CanvasCubit extends Cubit { _updateState(textItems: updatedItems); } - // method to moveText and emit changes + // method to moveText and emit changes (without history) void moveText(int index, double x, double y) { final updatedItems = List.from(state.textItems); updatedItems[index] = updatedItems[index].copyWith(x: x, y: y); + emit(state.copyWith(textItems: updatedItems)); + } + + // method to moveText and add to history (called when drag ends) + void moveTextWithHistory(int index, double x, double y) { + final updatedItems = List.from(state.textItems); + updatedItems[index] = updatedItems[index].copyWith(x: x, y: y); + _updateState(textItems: updatedItems); + } + + // method to rotate text and emit changes (without history) + void rotateText(int index, double rotation) { + if (index < 0 || index >= state.textItems.length) return; + final updatedItems = List.from(state.textItems); + updatedItems[index] = updatedItems[index].copyWith(rotation: rotation); + emit(state.copyWith(textItems: updatedItems)); + } + + // method to rotate text and add to history (called when rotation ends) + void rotateTextWithHistory(int index, double rotation) { + if (index < 0 || index >= state.textItems.length) return; + final updatedItems = List.from(state.textItems); + updatedItems[index] = updatedItems[index].copyWith(rotation: rotation); _updateState(textItems: updatedItems); } diff --git a/lib/models/text_item_model.dart b/lib/models/text_item_model.dart index 6962cf7e..ff0e5a99 100644 --- a/lib/models/text_item_model.dart +++ b/lib/models/text_item_model.dart @@ -1,4 +1,3 @@ -import 'dart:ui'; import 'package:flutter/material.dart'; class TextItem { @@ -14,13 +13,16 @@ class TextItem { final bool isHighlighted; final Color? highlightColor; final TextAlign textAlign; - + // Shadow properties final bool hasShadow; final Color shadowColor; final double shadowBlurRadius; final Offset shadowOffset; + // Rotation in radians + final double rotation; + TextItem({ required this.text, required this.x, @@ -38,6 +40,7 @@ class TextItem { this.shadowColor = Colors.black, this.shadowBlurRadius = 4.0, this.shadowOffset = const Offset(2.0, 2.0), + this.rotation = 0.0, }); TextItem copyWith({ @@ -57,6 +60,7 @@ class TextItem { Color? shadowColor, double? shadowBlurRadius, Offset? shadowOffset, + double? rotation, }) { return TextItem( text: text ?? this.text, @@ -75,6 +79,7 @@ class TextItem { shadowColor: shadowColor ?? this.shadowColor, shadowBlurRadius: shadowBlurRadius ?? this.shadowBlurRadius, shadowOffset: shadowOffset ?? this.shadowOffset, + rotation: rotation ?? this.rotation, ); } @@ -87,7 +92,7 @@ class TextItem { final backgroundColor = isHighlighted && highlightColor != null ? 'background-color: #${highlightColor!.toARGB32().toRadixString(16).padLeft(8, '0').substring(2)}; ' : ''; - + final textShadow = hasShadow ? 'text-shadow: ${shadowOffset.dx}px ${shadowOffset.dy}px ${shadowBlurRadius}px #${shadowColor.toARGB32().toRadixString(16).padLeft(8, '0').substring(2)}; ' : ''; @@ -117,6 +122,7 @@ class TextItem { 'shadowBlurRadius': shadowBlurRadius, 'shadowOffsetDx': shadowOffset.dx, 'shadowOffsetDy': shadowOffset.dy, + 'rotation': rotation, }; } @@ -134,17 +140,19 @@ class TextItem { isUnderlined: json['isUnderlined'] ?? false, textAlign: TextAlign.values[json['textAlign'] ?? 0], isHighlighted: json['isHighlighted'] ?? false, - highlightColor: json['highlightColor'] != null - ? Color(json['highlightColor']) - : null, + highlightColor: + json['highlightColor'] != null ? Color(json['highlightColor']) : null, hasShadow: json['hasShadow'] ?? false, - shadowColor: json['shadowColor'] != null - ? Color(json['shadowColor']) + shadowColor: json['shadowColor'] != null + ? Color(json['shadowColor']) : Colors.black, shadowBlurRadius: json['shadowBlurRadius']?.toDouble() ?? 4.0, - shadowOffset: json['shadowOffsetDx'] != null && json['shadowOffsetDy'] != null - ? Offset(json['shadowOffsetDx'].toDouble(), json['shadowOffsetDy'].toDouble()) - : const Offset(2.0, 2.0), + shadowOffset: + json['shadowOffsetDx'] != null && json['shadowOffsetDy'] != null + ? Offset(json['shadowOffsetDx'].toDouble(), + json['shadowOffsetDy'].toDouble()) + : const Offset(2.0, 2.0), + rotation: json['rotation']?.toDouble() ?? 0.0, ); } -} \ No newline at end of file +} diff --git a/lib/ui/screens/canvas_screen.dart b/lib/ui/screens/canvas_screen.dart index a1df6491..7418282b 100644 --- a/lib/ui/screens/canvas_screen.dart +++ b/lib/ui/screens/canvas_screen.dart @@ -9,7 +9,7 @@ import 'package:texterra/ui/screens/saved_pages.dart'; import '../../constants/color_constants.dart'; import '../../cubit/canvas_cubit.dart'; import '../../cubit/canvas_state.dart'; -import '../widgets/editable_text_widget.dart'; +import '../widgets/text_box_widget.dart'; import '../widgets/font_controls.dart'; import '../widgets/background_color_tray.dart'; import '../widgets/background_options_sheet.dart'; @@ -270,7 +270,7 @@ class CanvasScreen extends StatelessWidget { top: state.textItems[index].y, child: IgnorePointer( ignoring: state.isDrawingMode, - child: _DraggableText( + child: _DraggableTextBox( key: ValueKey('text_item_$index'), index: index, textItem: state.textItems[index], @@ -529,12 +529,12 @@ class CanvasScreen extends StatelessWidget { } } -class _DraggableText extends StatefulWidget { +class _DraggableTextBox extends StatefulWidget { final int index; final TextItem textItem; final bool isSelected; - const _DraggableText({ + const _DraggableTextBox({ super.key, required this.index, required this.textItem, @@ -542,37 +542,30 @@ class _DraggableText extends StatefulWidget { }); @override - State<_DraggableText> createState() => _DraggableTextState(); + State<_DraggableTextBox> createState() => _DraggableTextBoxState(); } -class _DraggableTextState extends State<_DraggableText> - with AutomaticKeepAliveClientMixin { +class _DraggableTextBoxState extends State<_DraggableTextBox> { Offset? _startPosition; Offset? _dragStartPosition; - - @override - bool get wantKeepAlive => true; + bool _isRotating = false; @override Widget build(BuildContext context) { - super.build(context); return GestureDetector( - onTap: () { - // Select this text item when tapped - context.read().selectText(widget.index); - }, onPanStart: (details) { - // Select this text item when starting to drag - context.read().selectText(widget.index); - _startPosition = Offset(widget.textItem.x, widget.textItem.y); - _dragStartPosition = details.localPosition; + if (!_isRotating) { + context.read().selectText(widget.index); + _startPosition = Offset(widget.textItem.x, widget.textItem.y); + _dragStartPosition = details.localPosition; + } }, onPanUpdate: (details) { - if (_startPosition != null && _dragStartPosition != null) { + if (!_isRotating && + _startPosition != null && + _dragStartPosition != null) { final delta = details.localPosition - _dragStartPosition!; final newPosition = _startPosition! + delta; - - // Update position in real-time during drag context.read().moveText( widget.index, newPosition.dx, @@ -580,15 +573,30 @@ class _DraggableTextState extends State<_DraggableText> ); } }, - onPanEnd: (_) { - _startPosition = null; - _dragStartPosition = null; + onPanEnd: (details) { + if (!_isRotating && + _startPosition != null && + _dragStartPosition != null) { + final delta = details.localPosition - _dragStartPosition!; + final newPosition = _startPosition! + delta; + context.read().moveTextWithHistory( + widget.index, + newPosition.dx, + newPosition.dy, + ); + _startPosition = null; + _dragStartPosition = null; + } }, - behavior: HitTestBehavior.opaque, - child: EditableTextWidget( + child: TextBoxWidget( index: widget.index, textItem: widget.textItem, isSelected: widget.isSelected, + onRotationStateChanged: (isRotating) { + setState(() { + _isRotating = isRotating; + }); + }, ), ); } diff --git a/lib/ui/widgets/text_box_widget.dart b/lib/ui/widgets/text_box_widget.dart new file mode 100644 index 00000000..d19af5c8 --- /dev/null +++ b/lib/ui/widgets/text_box_widget.dart @@ -0,0 +1,352 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'dart:math' as math; + +import '../../cubit/canvas_cubit.dart'; +import '../../models/text_item_model.dart'; +import '../../constants/color_constants.dart'; + +class TextBoxWidget extends StatefulWidget { + final int index; + final TextItem textItem; + final bool isSelected; + final ValueChanged? onRotationStateChanged; + + const TextBoxWidget({ + super.key, + required this.index, + required this.textItem, + required this.isSelected, + this.onRotationStateChanged, + }); + + @override + State createState() => _TextBoxWidgetState(); +} + +class _TextBoxWidgetState extends State { + late TextPainter _textPainter; + bool _isRotating = false; + double? _startRotation; + Offset? _startPanPosition; + + @override + void initState() { + super.initState(); + _updateTextPainter(); + } + + @override + void didUpdateWidget(TextBoxWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.textItem != widget.textItem) { + _updateTextPainter(); + } + } + + void _updateTextPainter() { + _textPainter = TextPainter( + text: TextSpan( + text: widget.textItem.text, + style: GoogleFonts.getFont( + widget.textItem.fontFamily, + fontStyle: widget.textItem.fontStyle, + fontWeight: widget.textItem.fontWeight, + fontSize: widget.textItem.fontSize, + decoration: widget.textItem.isUnderlined + ? TextDecoration.underline + : TextDecoration.none, + color: widget.textItem.color, + backgroundColor: widget.textItem.isHighlighted && + widget.textItem.highlightColor != null + ? widget.textItem.highlightColor + : null, + shadows: widget.textItem.hasShadow + ? [ + Shadow( + color: widget.textItem.shadowColor, + offset: widget.textItem.shadowOffset, + blurRadius: widget.textItem.shadowBlurRadius, + ), + ] + : null, + ), + ), + textAlign: widget.textItem.textAlign, + textDirection: TextDirection.ltr, + ); + _textPainter.layout(); + } + + double _calculateAngle(Offset center, Offset point) { + return math.atan2(point.dy - center.dy, point.dx - center.dx); + } + + @override + Widget build(BuildContext context) { + final textSize = _textPainter.size; + final padding = 8.0; + final boxWidth = textSize.width + (padding * 2); + final boxHeight = textSize.height + (padding * 2); + + return GestureDetector( + onTap: () async { + context.read().selectText(widget.index); + + // Open edit dialog + final result = await showDialog( + context: context, + builder: (context) => + _EditTextDialog(initialText: widget.textItem.text), + ); + + if (!context.mounted) return; + if (result == '_delete_') { + context.read().deleteText(widget.index); + } else if (result != null) { + context.read().editText(widget.index, result); + } + }, + child: Transform.rotate( + angle: widget.textItem.rotation, + alignment: Alignment.center, + child: Container( + width: boxWidth, + height: boxHeight, + decoration: BoxDecoration( + border: widget.isSelected + ? Border.all( + color: ColorConstants.dialogButtonBlue, + width: 2, + ) + : null, + borderRadius: BorderRadius.circular(4), + color: widget.isSelected + ? ColorConstants.highlightYellow.withValues(alpha: 0.1) + : Colors.transparent, + ), + child: Stack( + children: [ + // Text content + Positioned.fill( + child: Padding( + padding: EdgeInsets.all(padding), + child: CustomPaint( + painter: _TextPainter( + textPainter: _textPainter, + ), + ), + ), + ), + + // Rotation handle + if (widget.isSelected) + Positioned( + right: -12, + top: -12, + child: GestureDetector( + onPanStart: (details) { + setState(() => _isRotating = true); + _startRotation = widget.textItem.rotation; + final renderBox = context.findRenderObject() as RenderBox; + _startPanPosition = + renderBox.globalToLocal(details.globalPosition); + widget.onRotationStateChanged?.call(true); + }, + onPanUpdate: (details) { + if (_isRotating && + _startPanPosition != null && + _startRotation != null) { + final renderBox = + context.findRenderObject() as RenderBox; + final center = Offset(boxWidth / 2, boxHeight / 2); + final currentPos = + renderBox.globalToLocal(details.globalPosition); + + // Calculate the angle from center to start position + final startAngle = + _calculateAngle(center, _startPanPosition!); + // Calculate the angle from center to current position + final currentAngle = + _calculateAngle(center, currentPos); + // Calculate the delta rotation + final deltaRotation = currentAngle - startAngle; + // Apply the delta to the starting rotation + final newRotation = _startRotation! + deltaRotation; + + context + .read() + .rotateText(widget.index, newRotation); + } + }, + onPanEnd: (details) { + if (_isRotating && + _startPanPosition != null && + _startRotation != null) { + final renderBox = + context.findRenderObject() as RenderBox; + final center = Offset(boxWidth / 2, boxHeight / 2); + final currentPos = + renderBox.globalToLocal(details.globalPosition); + + // Calculate final rotation the same way as in onPanUpdate + final startAngle = + _calculateAngle(center, _startPanPosition!); + final currentAngle = + _calculateAngle(center, currentPos); + final deltaRotation = currentAngle - startAngle; + final newRotation = _startRotation! + deltaRotation; + + context + .read() + .rotateTextWithHistory(widget.index, newRotation); + } + setState(() => _isRotating = false); + _startPanPosition = null; + _startRotation = null; + widget.onRotationStateChanged?.call(false); + }, + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: ColorConstants.dialogButtonBlue, + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, + width: 2, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.3), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: const Icon( + Icons.refresh, + color: Colors.white, + size: 14, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} + +class _TextPainter extends CustomPainter { + final TextPainter textPainter; + + _TextPainter({required this.textPainter}); + + @override + void paint(Canvas canvas, Size size) { + textPainter.paint(canvas, Offset.zero); + } + + @override + bool shouldRepaint(_TextPainter oldDelegate) { + return textPainter != oldDelegate.textPainter; + } +} + +class _EditTextDialog extends StatelessWidget { + final String initialText; + + const _EditTextDialog({required this.initialText}); + + @override + Widget build(BuildContext context) { + final GlobalKey formKey = GlobalKey(); + final controller = TextEditingController(text: initialText); + + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Text( + 'Edit Text', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), + Form( + key: formKey, + child: TextFormField( + controller: controller, + validator: (value) => (value == null || value.trim().isEmpty) + ? 'Text cannot be empty' + : null, + decoration: InputDecoration( + labelText: 'Text', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide( + color: ColorConstants.dialogPurple, width: 2), + ), + ), + ), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context, '_delete_'), + child: const Text( + 'Remove', + style: TextStyle(color: ColorConstants.gray600), + ), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: () { + final trimmedText = controller.text.trim(); + + if (trimmedText.isEmpty) { + formKey.currentState?.validate(); + } else { + Navigator.pop(context, trimmedText); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: + ColorConstants.dialogPurple.withValues(alpha: 0.2), + foregroundColor: ColorConstants.dialogPurple, + elevation: 0, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text('Save'), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 40cd4fa5..02d75ecc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.dev" + source: hosted + version: "85.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + url: "https://pub.dev" + source: hosted + version: "7.7.1" ansicolor: dependency: transitive description: @@ -41,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "9.0.0" + bloc_test: + dependency: "direct main" + description: + name: bloc_test + sha256: "1dd549e58be35148bc22a9135962106aa29334bc1e3f285994946a1057b29d7b" + url: "https://pub.dev" + source: hosted + version: "10.0.0" boolean_selector: dependency: transitive description: @@ -65,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -89,6 +121,22 @@ packages: 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" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" cross_file: dependency: transitive description: @@ -137,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.3" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" fake_async: dependency: transitive description: @@ -272,6 +328,22 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" google_fonts: dependency: "direct main" description: @@ -296,6 +368,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" http_parser: dependency: transitive description: @@ -376,6 +456,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" irondash_engine_context: dependency: transitive description: @@ -392,6 +480,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" json_annotation: dependency: transitive description: @@ -432,6 +528,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -464,6 +568,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" nested: dependency: transitive description: @@ -472,6 +584,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + 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: @@ -560,6 +688,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" posix: dependency: transitive description: @@ -576,6 +712,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" shared_preferences: dependency: "direct main" description: @@ -632,11 +776,59 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -701,6 +893,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + url: "https://pub.dev" + source: hosted + version: "1.25.15" test_api: dependency: transitive description: @@ -709,6 +909,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + test_core: + dependency: transitive + description: + name: test_core + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + url: "https://pub.dev" + source: hosted + version: "0.6.8" typed_data: dependency: transitive description: @@ -749,6 +957,14 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" + url: "https://pub.dev" + source: hosted + version: "1.1.4" web: dependency: transitive description: @@ -757,6 +973,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" win32: dependency: transitive description: