diff --git a/lib/src/components/image_editor/image_editor.dart b/lib/src/components/image_editor/image_editor.dart deleted file mode 100644 index 0bed9a3..0000000 --- a/lib/src/components/image_editor/image_editor.dart +++ /dev/null @@ -1,524 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:hand_signature/signature.dart'; -import 'package:twonly/src/components/image_editor/data/image_item.dart'; -import 'package:twonly/src/components/image_editor/data/layer.dart'; -import 'package:twonly/src/components/image_editor/layers_viewer.dart'; -import 'package:twonly/src/components/image_editor/modules/all_emojis.dart'; -import 'package:screenshot/screenshot.dart'; - -List layers = []; -List undoLayers = []; -List removedLayers = []; - -class ImageEditor extends StatefulWidget { - final dynamic image; - final String? savePath; - - const ImageEditor({ - super.key, - this.image, - this.savePath, - }); - - @override - createState() => _ImageEditorState(); -} - -class _ImageEditorState extends State { - ImageItem currentImage = ImageItem(); - ScreenshotController screenshotController = ScreenshotController(); - - @override - void dispose() { - layers.clear(); - super.dispose(); - } - - List get filterActions { - return [ - IconButton( - icon: FaIcon( - FontAwesomeIcons.xmark, - size: 30, - shadows: [ - Shadow( - color: const Color.fromARGB(122, 0, 0, 0), - blurRadius: 1.0, - ) - ], - ), - color: Colors.white, - onPressed: () async { - Navigator.pop(context); - }, - ), - Expanded(child: Container()), - IconButton( - padding: const EdgeInsets.symmetric(horizontal: 8), - icon: Icon(Icons.undo, - color: layers.length > 1 || removedLayers.isNotEmpty - ? Colors.white - : Colors.grey), - onPressed: () { - if (removedLayers.isNotEmpty) { - layers.add(removedLayers.removeLast()); - setState(() {}); - return; - } - if (layers.length <= 1) return; // do not remove image layer - undoLayers.add(layers.removeLast()); - setState(() {}); - }, - ), - IconButton( - padding: const EdgeInsets.symmetric(horizontal: 8), - icon: Icon(Icons.redo, - color: undoLayers.isNotEmpty ? Colors.white : Colors.grey), - onPressed: () { - if (undoLayers.isEmpty) return; - - layers.add(undoLayers.removeLast()); - - setState(() {}); - }, - ), - const SizedBox(width: 50) - ]; - } - - @override - void initState() { - if (widget.image != null) { - loadImage(widget.image!); - } - - super.initState(); - } - - double widthRatio = 1, heightRatio = 1, pixelRatio = 1; - - /// obtain image Uint8List by merging layers - Future getMergedImage() async { - Uint8List? image; - - if (layers.length > 1) { - image = await screenshotController.capture(pixelRatio: pixelRatio); - } else if (layers.length == 1) { - if (layers.first is BackgroundLayerData) { - image = (layers.first as BackgroundLayerData).image.bytes; - } else if (layers.first is ImageLayerData) { - image = (layers.first as ImageLayerData).image.bytes; - } - } - return image; - } - - @override - Widget build(BuildContext context) { - pixelRatio = MediaQuery.of(context).devicePixelRatio; - - return Stack(children: [ - SizedBox( - height: currentImage.height / pixelRatio, - width: currentImage.width / pixelRatio, - child: Screenshot( - controller: screenshotController, - child: LayersViewer( - layers: layers, - onUpdate: () { - setState(() {}); - }, - editable: true, - ), - ), - ), - Positioned( - top: 5, - left: 0, - right: 0, - child: SafeArea( - child: Row( - children: filterActions, - ), - ), - ), - Positioned( - right: 0, - top: 100, - child: Container( - // color: Colors.black45, - alignment: Alignment.bottomCenter, - // height: 86 + MediaQuery.of(context).padding.bottom, - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: const BoxDecoration( - // color: Colors.black87, - // shape: BoxShape.rectangle, - // boxShadow: [ - // BoxShadow(blurRadius: 1), - // ], - ), - child: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - BottomButton( - icon: FontAwesomeIcons.font, - onTap: () async { - undoLayers.clear(); - removedLayers.clear(); - - layers.add( - TextLayerData( - text: "Test", - ), - ); - - setState(() {}); - }, - ), - SizedBox(height: 20), - BottomButton( - icon: FontAwesomeIcons.pencil, - onTap: () async { - var drawing = await Navigator.push( - context, - PageRouteBuilder( - opaque: false, - pageBuilder: (context, a, b) => ImageEditorDrawing( - image: currentImage, - ), - transitionDuration: Duration.zero, - reverseTransitionDuration: Duration.zero, - ), - ); - - if (drawing != null) { - undoLayers.clear(); - removedLayers.clear(); - - layers.add( - ImageLayerData( - image: ImageItem(drawing), - offset: Offset(0, 0), - ), - ); - - setState(() {}); - } - }, - ), - SizedBox(height: 20), - BottomButton( - icon: FontAwesomeIcons.faceGrinWide, - onTap: () async { - EmojiLayerData? layer = await showModalBottomSheet( - context: context, - backgroundColor: Colors.black, - builder: (BuildContext context) { - return const Emojis(); - }, - ); - - if (layer == null) return; - - undoLayers.clear(); - removedLayers.clear(); - layers.add(layer); - - setState(() {}); - }, - ), - ], - ), - ), - ), - ), - ]); - } - - Future loadImage(dynamic imageFile) async { - await currentImage.load(imageFile); - - layers.clear(); - - layers.add(BackgroundLayerData( - image: currentImage, - )); - - setState(() {}); - } -} - -/// Button used in bottomNavigationBar in ImageEditor -class BottomButton extends StatelessWidget { - final VoidCallback? onTap, onLongPress; - final IconData icon; - - const BottomButton({ - super.key, - this.onTap, - this.onLongPress, - required this.icon, - }); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - onLongPress: onLongPress, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Column( - children: [ - Icon( - icon, - color: Colors.white, - ), - const SizedBox(height: 8), - ], - ), - ), - ); - } -} - -/// Show image drawing surface over image -class ImageEditorDrawing extends StatefulWidget { - final ImageItem image; - - const ImageEditorDrawing({ - super.key, - required this.image, - }); - - @override - State createState() => _ImageEditorDrawingState(); -} - -class _ImageEditorDrawingState extends State { - Color pickerColor = Colors.white, currentColor = Colors.white; - - var screenshotController = ScreenshotController(); - - final control = HandSignatureControl( - threshold: 3.0, - smoothRatio: 0.65, - velocityRange: 2.0, - ); - - List undoList = []; - bool skipNextEvent = false; - - // void changeColor(Colors color) { - // currentColor = color.color; - // currentBackgroundColor = color.background; - - // setState(() {}); - // } - - @override - void initState() { - control.addListener(() { - if (control.hasActivePath) return; - - if (skipNextEvent) { - skipNextEvent = false; - return; - } - - undoList = []; - setState(() {}); - }); - - super.initState(); - } - - @override - Widget build(BuildContext context) { - final colors = [ - Colors.black, - Colors.white, - Colors.blue, - Colors.green, - Colors.pink, - Colors.purple, - Colors.brown, - Colors.indigo, - ]; - - return Scaffold( - backgroundColor: Colors.red.withAlpha(0), - body: SafeArea( - child: Stack( - fit: StackFit.expand, - children: [ - Positioned.fill( - top: 0, - child: Container( - height: 600, - width: 300, - decoration: BoxDecoration( - color: const Color.fromARGB(0, 210, 7, 7), - ), - // child: Container(), - child: Screenshot( - controller: screenshotController, - // image: widget.options.showBackground - // ? DecorationImage( - // image: Image.memory(widget.image.bytes).image, - // fit: BoxFit.contain, - // ) - // : null, - // child: Container(), - child: HandSignature( - control: control, - color: currentColor, - width: 1.0, - maxWidth: 7.0, - type: SignatureDrawType.shape, - ), - ), - ), - ), - Positioned( - top: 100, - right: 50, - child: Column( - children: [ - IconButton( - padding: const EdgeInsets.symmetric(horizontal: 8), - icon: const Icon(Icons.clear), - onPressed: () { - Navigator.pop(context); - }, - ), - IconButton( - padding: const EdgeInsets.symmetric(horizontal: 8), - icon: Icon( - Icons.undo, - color: control.paths.isNotEmpty - ? Colors.white - : Colors.white.withAlpha(80), - ), - onPressed: () { - if (control.paths.isEmpty) return; - skipNextEvent = true; - undoList.add(control.paths.last); - control.stepBack(); - setState(() {}); - }, - ), - IconButton( - padding: const EdgeInsets.symmetric(horizontal: 8), - icon: Icon( - Icons.redo, - color: undoList.isNotEmpty - ? Colors.white - : Colors.white.withAlpha(80), - ), - onPressed: () { - if (undoList.isEmpty) return; - - control.paths.add(undoList.removeLast()); - setState(() {}); - }, - ), - IconButton( - padding: const EdgeInsets.symmetric(horizontal: 8), - icon: const Icon(Icons.check), - onPressed: () async { - if (control.paths.isEmpty) return Navigator.pop(context); - - var data = await control.toImage( - color: currentColor, - height: widget.image.height, - width: widget.image.width, - ); - - if (!mounted) return; - - return Navigator.pop(context, data!.buffer.asUint8List()); - - // var loadingScreen = showLoadingScreen(context); - // var image = await screenshotController.capture(); - // loadingScreen.hide(); - - // if (!mounted) return; - - // return Navigator.pop(context, image); - }, - ), - ], - ), - ), - Positioned( - right: 0, - top: 50, - child: Container( - child: Container( - // height: 80, - padding: EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - color: const Color.fromARGB(130, 0, 0, 0), - borderRadius: BorderRadius.all(Radius.circular(10)), - ), - child: Column( - children: [ - for (var color in colors) - ColorButton( - color: color, - onTap: (color) { - currentColor = color; - setState(() {}); - }, - isSelected: color == currentColor, - ), - ], - ), - ), - ), - ), - ], - ), - ), - ); - } -} - -class ColorButton extends StatelessWidget { - final Color color; - final Function(Color) onTap; - final bool isSelected; - - const ColorButton({ - super.key, - required this.color, - required this.onTap, - this.isSelected = false, - }); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - onTap(color); - }, - child: Container( - height: 17, - width: 17, - margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: isSelected ? Colors.white : Colors.white54, - width: isSelected ? 2 : 1, - ), - ), - ), - ); - } -} diff --git a/lib/src/components/image_editor/layers/background_layer.dart b/lib/src/components/image_editor/layers/background_layer.dart index 8ec78e6..994682c 100755 --- a/lib/src/components/image_editor/layers/background_layer.dart +++ b/lib/src/components/image_editor/layers/background_layer.dart @@ -24,7 +24,7 @@ class _BackgroundLayerState extends State { return Container( width: widget.layerData.image.width.toDouble(), height: widget.layerData.image.height.toDouble(), - // color: black, + // color: Theme.of(context).colorScheme.surface, padding: EdgeInsets.zero, child: Image.memory(widget.layerData.image.bytes), ); diff --git a/lib/src/components/media_view_sizing.dart b/lib/src/components/media_view_sizing.dart new file mode 100644 index 0000000..ebb06f5 --- /dev/null +++ b/lib/src/components/media_view_sizing.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class MediaViewSizing extends StatelessWidget { + final Widget child; + + const MediaViewSizing(this.child, {super.key}); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Align( + alignment: Alignment.topCenter, + child: AspectRatio( + aspectRatio: 9 / 16, + // padding: EdgeInsets.symmetric(vertical: 50, horizontal: 0), + child: ClipRRect( + borderRadius: BorderRadius.circular(22), + child: child, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/views/camera_preview_view.dart b/lib/src/views/camera_preview_view.dart index bc522bd..970ede0 100644 --- a/lib/src/views/camera_preview_view.dart +++ b/lib/src/views/camera_preview_view.dart @@ -1,8 +1,8 @@ import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:camerawesome/camerawesome_plugin.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:twonly/src/components/media_view_sizing.dart'; import 'package:twonly/src/views/permissions_view.dart'; import 'package:twonly/src/views/share_image_editor_view.dart'; @@ -45,6 +45,7 @@ class CameraPreviewView extends StatefulWidget { class _CameraPreviewViewState extends State { double _lastZoom = 1; double _basePanY = 0; + bool sharePreviewIsShown = false; final GlobalKey navigatorKey = GlobalKey(); @override @@ -54,11 +55,11 @@ class _CameraPreviewViewState extends State { @override Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.symmetric(vertical: 50, horizontal: 0), - child: ClipRRect( + return MediaViewSizing( + ClipRRect( borderRadius: BorderRadius.circular(22), child: CameraAwesomeBuilder.custom( + previewAlignment: Alignment.topLeft, sensorConfig: SensorConfig.single( aspectRatio: CameraAspectRatios.ratio_16_9, zoom: 0.07, @@ -73,8 +74,11 @@ class _CameraPreviewViewState extends State { event.captureRequest.when( single: (single) async { final imageBytes = await single.file?.readAsBytes(); - if (imageBytes == null) return; - Navigator.push( + if (imageBytes == null || !context.mounted) return; + setState(() { + sharePreviewIsShown = true; + }); + await Navigator.push( context, PageRouteBuilder( opaque: false, @@ -88,6 +92,9 @@ class _CameraPreviewViewState extends State { reverseTransitionDuration: Duration.zero, ), ); + setState(() { + sharePreviewIsShown = false; + }); }, multiple: (multiple) { multiple.fileBySensor.forEach((key, value) { @@ -117,36 +124,36 @@ class _CameraPreviewViewState extends State { } }, builder: (cameraState, preview) { - return Container( - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - Positioned.fill( - child: GestureDetector( - onPanStart: (details) async { + return Stack( + alignment: Alignment.bottomCenter, + children: [ + Positioned.fill( + child: GestureDetector( + onPanStart: (details) async { + setState(() { + _basePanY = details.localPosition.dy; + }); + }, + onPanUpdate: (details) async { + var diff = _basePanY - details.localPosition.dy; + if (diff > 200) diff = 200; + if (diff < 0) diff = 0; + var tmp = (diff / 200 * 50).toInt() / 50; + if (tmp != _lastZoom) { + cameraState.sensorConfig.setZoom(tmp); setState(() { - _basePanY = details.localPosition.dy; + (tmp); + _lastZoom = tmp; }); - }, - onPanUpdate: (details) async { - var diff = _basePanY - details.localPosition.dy; - if (diff > 200) diff = 200; - if (diff < 0) diff = 0; - var tmp = (diff / 200 * 50).toInt() / 50; - if (tmp != _lastZoom) { - cameraState.sensorConfig.setZoom(tmp); - setState(() { - (tmp); - _lastZoom = tmp; - }); - } - }, - onDoubleTap: () async { - cameraState.switchCameraSensor( - aspectRatio: CameraAspectRatios.ratio_16_9); - }, - ), + } + }, + onDoubleTap: () async { + cameraState.switchCameraSensor( + aspectRatio: CameraAspectRatios.ratio_16_9); + }, ), + ), + if (!sharePreviewIsShown) Positioned( bottom: 30, left: 0, @@ -186,8 +193,7 @@ class _CameraPreviewViewState extends State { ), ), ), - ], - ), + ], ); }, saveConfig: SaveConfig.photoAndVideo( diff --git a/lib/src/views/share_image_editor_view.dart b/lib/src/views/share_image_editor_view.dart index 93dc116..bb963e9 100644 --- a/lib/src/views/share_image_editor_view.dart +++ b/lib/src/views/share_image_editor_view.dart @@ -1,11 +1,21 @@ -import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:twonly/src/components/image_editor/image_editor.dart'; +import 'package:twonly/src/components/media_view_sizing.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/share_image_view.dart'; +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'package:twonly/src/components/image_editor/data/image_item.dart'; +import 'package:twonly/src/components/image_editor/data/layer.dart'; +import 'package:twonly/src/components/image_editor/layers_viewer.dart'; +import 'package:twonly/src/components/image_editor/modules/all_emojis.dart'; +import 'package:screenshot/screenshot.dart'; +import 'package:hand_signature/signature.dart'; + +List layers = []; +List undoLayers = []; +List removedLayers = []; class ShareImageEditorView extends StatefulWidget { const ShareImageEditorView({super.key, required this.imageBytes}); @@ -18,40 +28,237 @@ class ShareImageEditorView extends StatefulWidget { class _ShareImageEditorView extends State { bool _imageSaved = false; + ImageItem currentImage = ImageItem(); + ScreenshotController screenshotController = ScreenshotController(); + @override void initState() { + loadImage(widget.imageBytes); + super.initState(); } + @override + void dispose() { + layers.clear(); + super.dispose(); + } + + static List get iconShadow => [ + Shadow( + color: const Color.fromARGB(122, 0, 0, 0), + blurRadius: 5.0, + ) + ]; + + List get filterActions { + return [ + IconButton( + icon: FaIcon(FontAwesomeIcons.xmark, size: 30, shadows: iconShadow), + color: Colors.white, + onPressed: () async { + Navigator.pop(context); + }, + ), + Expanded(child: Container()), + IconButton( + padding: const EdgeInsets.symmetric(horizontal: 8), + icon: FaIcon(FontAwesomeIcons.rotateLeft, + color: layers.length > 1 || removedLayers.isNotEmpty + ? Colors.white + : Colors.grey, + shadows: iconShadow), + onPressed: () { + if (removedLayers.isNotEmpty) { + layers.add(removedLayers.removeLast()); + setState(() {}); + return; + } + if (layers.length <= 1) return; // do not remove image layer + undoLayers.add(layers.removeLast()); + setState(() {}); + }, + ), + IconButton( + padding: const EdgeInsets.symmetric(horizontal: 8), + icon: FaIcon(FontAwesomeIcons.rotateRight, + color: undoLayers.isNotEmpty ? Colors.white : Colors.grey, + shadows: iconShadow), + onPressed: () { + if (undoLayers.isEmpty) return; + + layers.add(undoLayers.removeLast()); + + setState(() {}); + }, + ), + const SizedBox(width: 70) + ]; + } + + double widthRatio = 1, heightRatio = 1, pixelRatio = 1; + + /// obtain image Uint8List by merging layers + Future getMergedImage() async { + Uint8List? image; + + if (layers.length > 1) { + image = await screenshotController.capture(pixelRatio: pixelRatio); + } else if (layers.length == 1) { + if (layers.first is BackgroundLayerData) { + image = (layers.first as BackgroundLayerData).image.bytes; + } else if (layers.first is ImageLayerData) { + image = (layers.first as ImageLayerData).image.bytes; + } + } + return image; + } + + Future loadImage(dynamic imageFile) async { + await currentImage.load(imageFile); + + layers.clear(); + + layers.add(BackgroundLayerData( + image: currentImage, + )); + + setState(() {}); + } + @override Widget build(BuildContext context) { + pixelRatio = MediaQuery.of(context).devicePixelRatio; + return Scaffold( - backgroundColor: true - ? Theme.of(context).colorScheme.surface - : Colors.white.withAlpha(0), + backgroundColor: Colors.white.withAlpha(0), body: Stack( fit: StackFit.expand, children: [ - Positioned( - top: 0, - bottom: 70, - left: 0, - right: 0, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 0), - child: ClipRRect( - borderRadius: BorderRadius.circular(22), - // child: Container(), - child: ImageEditor( - image: widget.imageBytes, + MediaViewSizing( + SizedBox( + height: currentImage.height / pixelRatio, + width: currentImage.width / pixelRatio, + child: Screenshot( + controller: screenshotController, + child: LayersViewer( + layers: layers, + onUpdate: () { + setState(() {}); + }, + editable: true, ), ), ), ), Positioned( - bottom: 70, + top: 5, left: 0, right: 0, + child: SafeArea( + child: Row( + children: filterActions, + ), + ), + ), + Positioned( + right: 0, + top: 100, + child: Container( + // color: Colors.black45, + alignment: Alignment.bottomCenter, + // height: 86 + MediaQuery.of(context).padding.bottom, + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: const BoxDecoration( + // color: Colors.black87, + // shape: BoxShape.rectangle, + // boxShadow: [ + // BoxShadow(blurRadius: 1), + // ], + ), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + BottomButton( + icon: FontAwesomeIcons.font, + onTap: () async { + undoLayers.clear(); + removedLayers.clear(); + + layers.add( + TextLayerData( + text: "Test", + ), + ); + + setState(() {}); + }, + ), + SizedBox(height: 20), + BottomButton( + icon: FontAwesomeIcons.pencil, + onTap: () async { + var drawing = await Navigator.push( + context, + PageRouteBuilder( + opaque: false, + pageBuilder: (context, a, b) => ImageEditorDrawing( + image: currentImage, + ), + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + ), + ); + + if (drawing != null) { + undoLayers.clear(); + removedLayers.clear(); + + layers.add( + ImageLayerData( + image: ImageItem(drawing), + offset: Offset(0, 0), + ), + ); + + setState(() {}); + } + }, + ), + SizedBox(height: 20), + BottomButton( + icon: FontAwesomeIcons.faceGrinWide, + onTap: () async { + EmojiLayerData? layer = await showModalBottomSheet( + context: context, + backgroundColor: Colors.black, + builder: (BuildContext context) { + return const Emojis(); + }, + ); + + if (layer == null) return; + + undoLayers.clear(); + removedLayers.clear(); + layers.add(layer); + + setState(() {}); + }, + ), + ], + ), + ), + ), + ), + ], + ), + bottomNavigationBar: Container( + color: Theme.of(context).colorScheme.surface, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.only(bottom: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -68,8 +275,9 @@ class _ShareImageEditorView extends State { : Theme.of(context).colorScheme.primary, ), onPressed: () async { - if (_imageSaved) return; - final res = await saveImageToGallery(widget.imageBytes); + Uint8List? imageBytes = await getMergedImage(); + if (imageBytes == null || !context.mounted) return; + final res = await saveImageToGallery(imageBytes); if (res == null) { setState(() { _imageSaved = true; @@ -86,11 +294,13 @@ class _ShareImageEditorView extends State { FilledButton.icon( icon: FaIcon(FontAwesomeIcons.solidPaperPlane), onPressed: () async { + Uint8List? imageBytes = await getMergedImage(); + if (imageBytes == null || !context.mounted) return; Navigator.push( context, MaterialPageRoute( builder: (context) => - ShareImageView(imageBytes: widget.imageBytes)), + ShareImageView(imageBytes: imageBytes)), ); }, style: ButtonStyle( @@ -106,7 +316,279 @@ class _ShareImageEditorView extends State { ], ), ), - ], + ), + ), + ); + } +} + +/// Button used in bottomNavigationBar in ImageEditor +class BottomButton extends StatelessWidget { + final VoidCallback? onTap, onLongPress; + final IconData icon; + + const BottomButton({ + super.key, + this.onTap, + this.onLongPress, + required this.icon, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + onLongPress: onLongPress, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + children: [ + FaIcon( + icon, + color: Colors.white, + shadows: _ShareImageEditorView.iconShadow, + ), + const SizedBox(height: 8), + ], + ), + ), + ); + } +} + +/// Show image drawing surface over image +class ImageEditorDrawing extends StatefulWidget { + final ImageItem image; + + const ImageEditorDrawing({ + super.key, + required this.image, + }); + + @override + State createState() => _ImageEditorDrawingState(); +} + +class _ImageEditorDrawingState extends State { + Color pickerColor = Colors.white, currentColor = Colors.white; + + var screenshotController = ScreenshotController(); + + final control = HandSignatureControl( + threshold: 3.0, + smoothRatio: 0.65, + velocityRange: 2.0, + ); + + List undoList = []; + bool skipNextEvent = false; + + // void changeColor(Colors color) { + // currentColor = color.color; + // currentBackgroundColor = color.background; + + // setState(() {}); + // } + + @override + void initState() { + control.addListener(() { + if (control.hasActivePath) return; + + if (skipNextEvent) { + skipNextEvent = false; + return; + } + + undoList = []; + setState(() {}); + }); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + final colors = [ + Colors.black, + Colors.white, + Colors.blue, + Colors.green, + Colors.pink, + Colors.purple, + Colors.brown, + Colors.indigo, + ]; + + return Scaffold( + backgroundColor: Colors.red.withAlpha(0), + body: SafeArea( + child: Stack( + fit: StackFit.expand, + children: [ + Positioned.fill( + top: 0, + child: Container( + height: 600, + width: 300, + decoration: BoxDecoration( + color: const Color.fromARGB(0, 210, 7, 7), + ), + // child: Container(), + child: Screenshot( + controller: screenshotController, + // image: widget.options.showBackground + // ? DecorationImage( + // image: Image.memory(widget.image.bytes).image, + // fit: BoxFit.contain, + // ) + // : null, + // child: Container(), + child: HandSignature( + control: control, + color: currentColor, + width: 1.0, + maxWidth: 7.0, + type: SignatureDrawType.shape, + ), + ), + ), + ), + Positioned( + top: 100, + right: 50, + child: Column( + children: [ + IconButton( + padding: const EdgeInsets.symmetric(horizontal: 8), + icon: const Icon(Icons.clear), + onPressed: () { + Navigator.pop(context); + }, + ), + IconButton( + padding: const EdgeInsets.symmetric(horizontal: 8), + icon: Icon( + Icons.undo, + color: control.paths.isNotEmpty + ? Colors.white + : Colors.white.withAlpha(80), + ), + onPressed: () { + if (control.paths.isEmpty) return; + skipNextEvent = true; + undoList.add(control.paths.last); + control.stepBack(); + setState(() {}); + }, + ), + IconButton( + padding: const EdgeInsets.symmetric(horizontal: 8), + icon: Icon( + Icons.redo, + color: undoList.isNotEmpty + ? Colors.white + : Colors.white.withAlpha(80), + ), + onPressed: () { + if (undoList.isEmpty) return; + + control.paths.add(undoList.removeLast()); + setState(() {}); + }, + ), + IconButton( + padding: const EdgeInsets.symmetric(horizontal: 8), + icon: const Icon(Icons.check), + onPressed: () async { + if (control.paths.isEmpty) return Navigator.pop(context); + + var data = await control.toImage( + color: currentColor, + height: widget.image.height, + width: widget.image.width, + ); + + if (!mounted) return; + + return Navigator.pop(context, data!.buffer.asUint8List()); + + // var loadingScreen = showLoadingScreen(context); + // var image = await screenshotController.capture(); + // loadingScreen.hide(); + + // if (!mounted) return; + + // return Navigator.pop(context, image); + }, + ), + ], + ), + ), + Positioned( + right: 0, + top: 50, + child: Container( + child: Container( + // height: 80, + padding: EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: const Color.fromARGB(130, 0, 0, 0), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + child: Column( + children: [ + for (var color in colors) + ColorButton( + color: color, + onTap: (color) { + currentColor = color; + setState(() {}); + }, + isSelected: color == currentColor, + ), + ], + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +class ColorButton extends StatelessWidget { + final Color color; + final Function(Color) onTap; + final bool isSelected; + + const ColorButton({ + super.key, + required this.color, + required this.onTap, + this.isSelected = false, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + onTap(color); + }, + child: Container( + height: 17, + width: 17, + margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isSelected ? Colors.white : Colors.white54, + width: isSelected ? 2 : 1, + ), + ), ), ); }