From d13de25f96a5e7ccdd04dd87715177d1e8662155 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sun, 2 Feb 2025 20:23:47 +0100 Subject: [PATCH] text fields editing works --- .../components/image_editor/data/layer.dart | 34 ++-- .../image_editor/layers/background_layer.dart | 2 - .../image_editor/layers/emoji_layer.dart | 30 ++-- .../image_editor/layers/image_layer.dart | 2 - .../image_editor/layers/text_layer.dart | 151 +++++++++++++++--- .../image_editor/layers_viewer.dart | 6 - lib/src/views/share_image_editor_view.dart | 76 +++++---- 7 files changed, 185 insertions(+), 116 deletions(-) diff --git a/lib/src/components/image_editor/data/layer.dart b/lib/src/components/image_editor/data/layer.dart index da65d43..0aef43b 100755 --- a/lib/src/components/image_editor/data/layer.dart +++ b/lib/src/components/image_editor/data/layer.dart @@ -5,10 +5,14 @@ import 'package:twonly/src/components/image_editor/data/image_item.dart'; class Layer { Offset offset; double rotation, scale, opacity; + bool isEditing; + bool isDeleted; Layer({ - this.offset = const Offset(64, 64), + this.offset = const Offset(0, 0), this.opacity = 1, + this.isEditing = false, + this.isDeleted = false, this.rotation = 0, this.scale = 1, }); @@ -35,6 +39,7 @@ class EmojiLayerData extends Layer { super.opacity, super.rotation, super.scale, + super.isEditing, }); } @@ -50,40 +55,19 @@ class ImageLayerData extends Layer { super.opacity, super.rotation, super.scale, + super.isEditing, }); } /// Attributes used by [TextLayer] class TextLayerData extends Layer { String text; - TextLayerData({ - required this.text, - super.offset, - super.opacity, - super.rotation, - super.scale, - }); -} - -/// Attributes used by [TextLayer] -class LinkLayerData extends Layer { - String text; - double size; - Color color, background; - double backgroundOpacity; - TextAlign align; - - LinkLayerData({ - required this.text, - this.size = 64, - this.color = Colors.white, - this.background = Colors.transparent, - this.backgroundOpacity = 0, - this.align = TextAlign.left, + this.text = "", super.offset, super.opacity, super.rotation, super.scale, + super.isEditing = true, }); } diff --git a/lib/src/components/image_editor/layers/background_layer.dart b/lib/src/components/image_editor/layers/background_layer.dart index 994682c..db65cd1 100755 --- a/lib/src/components/image_editor/layers/background_layer.dart +++ b/lib/src/components/image_editor/layers/background_layer.dart @@ -5,13 +5,11 @@ import 'package:twonly/src/components/image_editor/data/layer.dart'; class BackgroundLayer extends StatefulWidget { final BackgroundLayerData layerData; final VoidCallback? onUpdate; - final bool editable; const BackgroundLayer({ super.key, required this.layerData, this.onUpdate, - this.editable = false, }); @override diff --git a/lib/src/components/image_editor/layers/emoji_layer.dart b/lib/src/components/image_editor/layers/emoji_layer.dart index e966a40..4722dde 100755 --- a/lib/src/components/image_editor/layers/emoji_layer.dart +++ b/lib/src/components/image_editor/layers/emoji_layer.dart @@ -5,13 +5,11 @@ import 'package:twonly/src/components/image_editor/data/layer.dart'; class EmojiLayer extends StatefulWidget { final EmojiLayerData layerData; final VoidCallback? onUpdate; - final bool editable; const EmojiLayer({ super.key, required this.layerData, this.onUpdate, - this.editable = false, }); @override @@ -31,22 +29,20 @@ class _EmojiLayerState extends State { left: widget.layerData.offset.dx, top: widget.layerData.offset.dy, child: GestureDetector( - onTap: widget.editable ? () {} : null, - onScaleUpdate: widget.editable - ? (detail) { - if (detail.pointerCount == 1) { - widget.layerData.offset = Offset( - widget.layerData.offset.dx + detail.focalPointDelta.dx, - widget.layerData.offset.dy + detail.focalPointDelta.dy, - ); - } else if (detail.pointerCount == 2) { - widget.layerData.size = initialSize + - detail.scale * 5 * (detail.scale > 1 ? 1 : -1); - } + onTap: () {}, + onScaleUpdate: (detail) { + if (detail.pointerCount == 1) { + widget.layerData.offset = Offset( + widget.layerData.offset.dx + detail.focalPointDelta.dx, + widget.layerData.offset.dy + detail.focalPointDelta.dy, + ); + } else if (detail.pointerCount == 2) { + widget.layerData.size = + initialSize + detail.scale * 5 * (detail.scale > 1 ? 1 : -1); + } - setState(() {}); - } - : null, + setState(() {}); + }, child: Transform.rotate( angle: widget.layerData.rotation, child: Container( diff --git a/lib/src/components/image_editor/layers/image_layer.dart b/lib/src/components/image_editor/layers/image_layer.dart index 22d7207..d714556 100755 --- a/lib/src/components/image_editor/layers/image_layer.dart +++ b/lib/src/components/image_editor/layers/image_layer.dart @@ -5,13 +5,11 @@ import 'package:twonly/src/components/image_editor/data/layer.dart'; class ImageLayer extends StatefulWidget { final ImageLayerData layerData; final VoidCallback? onUpdate; - final bool editable; const ImageLayer({ super.key, required this.layerData, this.onUpdate, - this.editable = false, }); @override diff --git a/lib/src/components/image_editor/layers/text_layer.dart b/lib/src/components/image_editor/layers/text_layer.dart index 6cf47ed..67a7da2 100755 --- a/lib/src/components/image_editor/layers/text_layer.dart +++ b/lib/src/components/image_editor/layers/text_layer.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/components/image_editor/data/layer.dart'; +import 'package:twonly/src/views/share_image_editor_view.dart'; /// Text layer class TextLayer extends StatefulWidget { final TextLayerData layerData; final VoidCallback? onUpdate; - final bool editable; const TextLayer({ super.key, required this.layerData, this.onUpdate, - this.editable = false, }); @override createState() => _TextViewState(); @@ -19,41 +19,144 @@ class TextLayer extends StatefulWidget { class _TextViewState extends State { double initialRotation = 0; + bool deleteLayer = false; + bool isDeleted = false; + bool elementIsScaled = false; + final GlobalKey _widgetKey = GlobalKey(); // Create a GlobalKey + final TextEditingController textController = TextEditingController(); + + @override + void initState() { + super.initState(); + + if (widget.layerData.offset.dy == 0) { + // Set the initial offset to the center of the screen + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + widget.layerData.offset = + Offset(0, MediaQuery.of(context).size.height / 2 - 30); + }); + }); + } + } @override Widget build(BuildContext context) { - return Positioned( - left: 0, - right: 0, - top: widget.layerData.offset.dy, - child: GestureDetector( - onTap: widget.editable ? () {} : null, - onScaleUpdate: widget.editable - ? (detail) { - if (detail.pointerCount == 1) { - widget.layerData.offset = Offset( - 0, - widget.layerData.offset.dy + detail.focalPointDelta.dy, - ); - } - setState(() {}); - } - : null, + if (isDeleted) return Container(); + + if (widget.layerData.isEditing) { + return Positioned( + bottom: MediaQuery.of(context).viewInsets.bottom - 100, + left: 0, + right: 0, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.black.withAlpha(100), ), - child: Text( - widget.layerData.text.toString(), + child: TextField( + controller: textController, + autofocus: true, + onEditingComplete: () { + setState(() { + widget.layerData.isEditing = false; + widget.layerData.text = textController.text; + }); + }, + onTapOutside: (a) { + widget.layerData.text = textController.text; + Future.delayed(Duration(milliseconds: 100), () { + setState(() { + widget.layerData.isEditing = false; + }); + }); + }, + decoration: InputDecoration( + border: InputBorder.none, // Keine Umrandung + contentPadding: EdgeInsets.zero, // Kein Padding + ), + // widget.layerData.text.toString(), textAlign: TextAlign.center, style: TextStyle( color: Colors.white, - fontSize: 20, + fontSize: 17, ), ), ), - ), + ); + } + return Stack( + key: _widgetKey, + children: [ + Positioned( + left: 0, + right: 0, + top: widget.layerData.offset.dy, + child: GestureDetector( + onScaleStart: (d) { + setState(() { + elementIsScaled = true; + }); + }, + onScaleEnd: (d) { + if (deleteLayer) isDeleted = true; + elementIsScaled = false; + setState(() {}); + }, + onTap: () { + setState(() { + widget.layerData.isEditing = true; + }); + }, + onScaleUpdate: (detail) { + if (detail.pointerCount == 1) { + widget.layerData.offset = Offset( + 0, widget.layerData.offset.dy + detail.focalPointDelta.dy); + } + final RenderBox renderBox = + _widgetKey.currentContext!.findRenderObject() as RenderBox; + + if (widget.layerData.offset.dy > renderBox.size.height - 80) { + deleteLayer = true; + } else { + deleteLayer = false; + } + setState(() {}); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.black.withAlpha(100), + ), + child: Text( + widget.layerData.text, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 20, + ), + ), + ), + ), + ), + if (elementIsScaled) + Positioned( + left: 0, + right: 0, + bottom: 20, + child: Center( + child: GestureDetector( + onTapUp: (d) { + textController.text = ""; + }, + child: FaIcon( + FontAwesomeIcons.trashCan, + shadows: ShareImageEditorView.iconShadow, + color: deleteLayer ? Colors.red : Colors.white, + ), + ), + ), + ), + ], ); } } diff --git a/lib/src/components/image_editor/layers_viewer.dart b/lib/src/components/image_editor/layers_viewer.dart index 985e684..ca4e430 100644 --- a/lib/src/components/image_editor/layers_viewer.dart +++ b/lib/src/components/image_editor/layers_viewer.dart @@ -9,12 +9,10 @@ import 'package:twonly/src/components/image_editor/layers/text_layer.dart'; class LayersViewer extends StatelessWidget { final List layers; final Function()? onUpdate; - final bool editable; const LayersViewer({ super.key, required this.layers, - required this.editable, this.onUpdate, }); @@ -28,7 +26,6 @@ class LayersViewer extends StatelessWidget { return BackgroundLayer( layerData: layerItem, onUpdate: onUpdate, - editable: editable, ); } @@ -37,7 +34,6 @@ class LayersViewer extends StatelessWidget { return ImageLayer( layerData: layerItem, onUpdate: onUpdate, - editable: editable, ); } @@ -46,7 +42,6 @@ class LayersViewer extends StatelessWidget { return EmojiLayer( layerData: layerItem, onUpdate: onUpdate, - editable: editable, ); } @@ -55,7 +50,6 @@ class LayersViewer extends StatelessWidget { return TextLayer( layerData: layerItem, onUpdate: onUpdate, - editable: editable, ); } diff --git a/lib/src/views/share_image_editor_view.dart b/lib/src/views/share_image_editor_view.dart index bb963e9..3cc8288 100644 --- a/lib/src/views/share_image_editor_view.dart +++ b/lib/src/views/share_image_editor_view.dart @@ -20,7 +20,12 @@ List removedLayers = []; class ShareImageEditorView extends StatefulWidget { const ShareImageEditorView({super.key, required this.imageBytes}); final Uint8List imageBytes; - + static List get iconShadow => [ + Shadow( + color: const Color.fromARGB(122, 0, 0, 0), + blurRadius: 5.0, + ) + ]; @override State createState() => _ShareImageEditorView(); } @@ -44,17 +49,11 @@ class _ShareImageEditorView extends State { 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), + icon: FaIcon(FontAwesomeIcons.xmark, + size: 30, shadows: ShareImageEditorView.iconShadow), color: Colors.white, onPressed: () async { Navigator.pop(context); @@ -67,13 +66,14 @@ class _ShareImageEditorView extends State { color: layers.length > 1 || removedLayers.isNotEmpty ? Colors.white : Colors.grey, - shadows: iconShadow), + shadows: ShareImageEditorView.iconShadow), onPressed: () { if (removedLayers.isNotEmpty) { layers.add(removedLayers.removeLast()); setState(() {}); return; } + layers = layers.where((x) => !x.isDeleted).toList(); if (layers.length <= 1) return; // do not remove image layer undoLayers.add(layers.removeLast()); setState(() {}); @@ -83,7 +83,7 @@ class _ShareImageEditorView extends State { padding: const EdgeInsets.symmetric(horizontal: 8), icon: FaIcon(FontAwesomeIcons.rotateRight, color: undoLayers.isNotEmpty ? Colors.white : Colors.grey, - shadows: iconShadow), + shadows: ShareImageEditorView.iconShadow), onPressed: () { if (undoLayers.isEmpty) return; @@ -132,21 +132,32 @@ class _ShareImageEditorView extends State { return Scaffold( backgroundColor: Colors.white.withAlpha(0), + resizeToAvoidBottomInset: false, body: Stack( fit: StackFit.expand, children: [ - MediaViewSizing( - SizedBox( - height: currentImage.height / pixelRatio, - width: currentImage.width / pixelRatio, - child: Screenshot( - controller: screenshotController, - child: LayersViewer( - layers: layers, - onUpdate: () { - setState(() {}); - }, - editable: true, + GestureDetector( + onTap: () { + if (layers.any((x) => x.isEditing)) { + return; + } + undoLayers.clear(); + removedLayers.clear(); + layers.add(TextLayerData()); + setState(() {}); + }, + child: MediaViewSizing( + SizedBox( + height: currentImage.height / pixelRatio, + width: currentImage.width / pixelRatio, + child: Screenshot( + controller: screenshotController, + child: LayersViewer( + layers: layers.where((x) => !x.isDeleted).toList(), + onUpdate: () { + setState(() {}); + }, + ), ), ), ), @@ -165,17 +176,8 @@ class _ShareImageEditorView extends State { 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, @@ -185,13 +187,7 @@ class _ShareImageEditorView extends State { onTap: () async { undoLayers.clear(); removedLayers.clear(); - - layers.add( - TextLayerData( - text: "Test", - ), - ); - + layers.add(TextLayerData()); setState(() {}); }, ), @@ -346,7 +342,7 @@ class BottomButton extends StatelessWidget { FaIcon( icon, color: Colors.white, - shadows: _ShareImageEditorView.iconShadow, + shadows: ShareImageEditorView.iconShadow, ), const SizedBox(height: 8), ],