mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 11:18:41 +00:00
text fields editing works
This commit is contained in:
parent
d72f0abfc0
commit
d13de25f96
7 changed files with 185 additions and 116 deletions
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<EmojiLayer> {
|
|||
left: widget.layerData.offset.dx,
|
||||
top: widget.layerData.offset.dy,
|
||||
child: GestureDetector(
|
||||
onTap: widget.editable ? () {} : null,
|
||||
onScaleUpdate: widget.editable
|
||||
? (detail) {
|
||||
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);
|
||||
widget.layerData.size =
|
||||
initialSize + detail.scale * 5 * (detail.scale > 1 ? 1 : -1);
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
: null,
|
||||
},
|
||||
child: Transform.rotate(
|
||||
angle: widget.layerData.rotation,
|
||||
child: Container(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,33 +19,116 @@ class TextLayer extends StatefulWidget {
|
|||
|
||||
class _TextViewState extends State<TextLayer> {
|
||||
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) {
|
||||
if (isDeleted) return Container();
|
||||
|
||||
if (widget.layerData.isEditing) {
|
||||
return Positioned(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom - 100,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(100),
|
||||
),
|
||||
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: 17,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Stack(
|
||||
key: _widgetKey,
|
||||
children: [
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: widget.layerData.offset.dy,
|
||||
child: GestureDetector(
|
||||
onTap: widget.editable ? () {} : null,
|
||||
onScaleUpdate: widget.editable
|
||||
? (detail) {
|
||||
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,
|
||||
);
|
||||
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(() {});
|
||||
}
|
||||
: null,
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(100),
|
||||
),
|
||||
child: Text(
|
||||
widget.layerData.text.toString(),
|
||||
widget.layerData.text,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
|
|
@ -54,6 +137,26 @@ class _TextViewState extends State<TextLayer> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,10 @@ import 'package:twonly/src/components/image_editor/layers/text_layer.dart';
|
|||
class LayersViewer extends StatelessWidget {
|
||||
final List<Layer> 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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,12 @@ List<Layer> removedLayers = [];
|
|||
class ShareImageEditorView extends StatefulWidget {
|
||||
const ShareImageEditorView({super.key, required this.imageBytes});
|
||||
final Uint8List imageBytes;
|
||||
|
||||
static List<Shadow> get iconShadow => [
|
||||
Shadow(
|
||||
color: const Color.fromARGB(122, 0, 0, 0),
|
||||
blurRadius: 5.0,
|
||||
)
|
||||
];
|
||||
@override
|
||||
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
||||
}
|
||||
|
|
@ -44,17 +49,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
static List<Shadow> get iconShadow => [
|
||||
Shadow(
|
||||
color: const Color.fromARGB(122, 0, 0, 0),
|
||||
blurRadius: 5.0,
|
||||
)
|
||||
];
|
||||
|
||||
List<Widget> 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<ShareImageEditorView> {
|
|||
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<ShareImageEditorView> {
|
|||
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<ShareImageEditorView> {
|
|||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white.withAlpha(0),
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
MediaViewSizing(
|
||||
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,
|
||||
layers: layers.where((x) => !x.isDeleted).toList(),
|
||||
onUpdate: () {
|
||||
setState(() {});
|
||||
},
|
||||
editable: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -165,17 +176,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
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<ShareImageEditorView> {
|
|||
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),
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue