mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:48:41 +00:00
image editor in a good state
This commit is contained in:
parent
5f45de620a
commit
5675437bc0
8 changed files with 67 additions and 132 deletions
|
|
@ -14,7 +14,7 @@ class ActionButton extends StatelessWidget {
|
|||
icon: FaIcon(
|
||||
icon,
|
||||
size: 30,
|
||||
color: color,
|
||||
color: color ?? Colors.white,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: const Color.fromARGB(122, 0, 0, 0),
|
||||
|
|
|
|||
|
|
@ -46,22 +46,6 @@ class EmojiLayerData extends Layer {
|
|||
});
|
||||
}
|
||||
|
||||
/// Attributes used by [ImageLayer]
|
||||
class ImageLayerData extends Layer {
|
||||
ImageItem image;
|
||||
double size;
|
||||
|
||||
ImageLayerData({
|
||||
required this.image,
|
||||
this.size = 64,
|
||||
super.offset,
|
||||
super.opacity,
|
||||
super.rotation,
|
||||
super.scale,
|
||||
super.isEditing,
|
||||
});
|
||||
}
|
||||
|
||||
/// Attributes used by [TextLayer]
|
||||
class TextLayerData extends Layer {
|
||||
String text;
|
||||
|
|
|
|||
|
|
@ -17,36 +17,66 @@ class EmojiLayer extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _EmojiLayerState extends State<EmojiLayer> {
|
||||
double initialSize = 0;
|
||||
double initialRotation = 0;
|
||||
Offset initialOffset = Offset.zero;
|
||||
double initialScale = 1.0;
|
||||
final GlobalKey _key = GlobalKey();
|
||||
|
||||
@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(
|
||||
MediaQuery.of(context).size.width / 2 - 64,
|
||||
MediaQuery.of(context).size.height / 2 - 64 - 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
initialSize = widget.layerData.size;
|
||||
initialRotation = widget.layerData.rotation;
|
||||
|
||||
return Positioned(
|
||||
left: widget.layerData.offset.dx,
|
||||
top: widget.layerData.offset.dy,
|
||||
child: GestureDetector(
|
||||
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);
|
||||
}
|
||||
onScaleStart: (details) {
|
||||
// Store the initial scale and rotation
|
||||
initialScale = widget.layerData.size; // Reset initial scale
|
||||
initialRotation = widget.layerData.rotation;
|
||||
initialOffset = widget.layerData.offset;
|
||||
},
|
||||
onScaleUpdate: (details) {
|
||||
setState(() {
|
||||
// Update the size based on the scale factor
|
||||
|
||||
setState(() {});
|
||||
widget.layerData.size = initialScale * details.scale;
|
||||
|
||||
// Update the rotation based on the rotation angle
|
||||
widget.layerData.rotation = initialRotation + details.rotation;
|
||||
|
||||
// Update the position based on the translation
|
||||
final RenderBox renderBox =
|
||||
_key.currentContext?.findRenderObject() as RenderBox;
|
||||
var dx = details.focalPoint.dx - (renderBox.size.width / 2);
|
||||
var dy = details.focalPoint.dy - (renderBox.size.height / 2 + 34);
|
||||
widget.layerData.offset = Offset(dx, dy);
|
||||
});
|
||||
},
|
||||
onScaleEnd: (details) {
|
||||
// Optionally, you can handle the end of the scale gesture here
|
||||
},
|
||||
child: Transform.rotate(
|
||||
key: _key,
|
||||
angle: widget.layerData.rotation,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(64),
|
||||
padding: const EdgeInsets.all(34),
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
widget.layerData.text.toString(),
|
||||
style: TextStyle(
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
||||
|
||||
/// Image layer that can be used to add overlay images and drawings
|
||||
class ImageLayer extends StatefulWidget {
|
||||
final ImageLayerData layerData;
|
||||
final VoidCallback? onUpdate;
|
||||
|
||||
const ImageLayer({
|
||||
super.key,
|
||||
required this.layerData,
|
||||
this.onUpdate,
|
||||
});
|
||||
|
||||
@override
|
||||
createState() => _ImageLayerState();
|
||||
}
|
||||
|
||||
class _ImageLayerState extends State<ImageLayer> {
|
||||
double initialSize = 0;
|
||||
double initialRotation = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
initialSize = widget.layerData.size;
|
||||
initialRotation = widget.layerData.rotation;
|
||||
|
||||
return Positioned.fill(
|
||||
child: SizedBox(
|
||||
width: widget.layerData.image.width.toDouble(),
|
||||
height: widget.layerData.image.height.toDouble(),
|
||||
child: Image.memory(widget.layerData.image.bytes),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ import 'package:twonly/src/components/image_editor/data/layer.dart';
|
|||
import 'package:twonly/src/components/image_editor/layers/background_layer.dart';
|
||||
import 'package:twonly/src/components/image_editor/layers/draw_layer.dart';
|
||||
import 'package:twonly/src/components/image_editor/layers/emoji_layer.dart';
|
||||
import 'package:twonly/src/components/image_editor/layers/image_layer.dart';
|
||||
import 'package:twonly/src/components/image_editor/layers/text_layer.dart';
|
||||
|
||||
/// View stacked layers (unbounded height, width)
|
||||
|
|
@ -22,50 +21,34 @@ class LayersViewer extends StatelessWidget {
|
|||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Background and Image layers at the bottom
|
||||
...layers
|
||||
.where((layerItem) =>
|
||||
layerItem is BackgroundLayerData || layerItem is ImageLayerData)
|
||||
.map((layerItem) {
|
||||
if (layerItem is BackgroundLayerData) {
|
||||
return BackgroundLayer(
|
||||
layerData: layerItem,
|
||||
onUpdate: onUpdate,
|
||||
);
|
||||
} else if (layerItem is ImageLayerData) {
|
||||
return ImageLayer(
|
||||
layerData: layerItem,
|
||||
onUpdate: onUpdate,
|
||||
);
|
||||
}
|
||||
return Container(); // Fallback, should not reach here
|
||||
}),
|
||||
|
||||
// Draw layer (if needed, can be placed anywhere)
|
||||
...layers.whereType<DrawLayerData>().map((layerItem) {
|
||||
return DrawLayer(
|
||||
...layers.whereType<BackgroundLayerData>().map((layerItem) {
|
||||
return BackgroundLayer(
|
||||
layerData: layerItem,
|
||||
onUpdate: onUpdate,
|
||||
);
|
||||
}),
|
||||
|
||||
// Emoji and Text layers at the top
|
||||
...layers
|
||||
.where((layerItem) =>
|
||||
layerItem is EmojiLayerData || layerItem is TextLayerData)
|
||||
layerItem is EmojiLayerData || layerItem is DrawLayerData)
|
||||
.map((layerItem) {
|
||||
if (layerItem is EmojiLayerData) {
|
||||
return EmojiLayer(
|
||||
layerData: layerItem,
|
||||
onUpdate: onUpdate,
|
||||
);
|
||||
} else if (layerItem is TextLayerData) {
|
||||
return TextLayer(
|
||||
} else if (layerItem is DrawLayerData) {
|
||||
return DrawLayer(
|
||||
layerData: layerItem,
|
||||
onUpdate: onUpdate,
|
||||
);
|
||||
}
|
||||
return Container(); // Fallback, should not reach here
|
||||
return Container();
|
||||
}),
|
||||
...layers.whereType<TextLayerData>().map((layerItem) {
|
||||
return TextLayer(
|
||||
layerData: layerItem,
|
||||
onUpdate: onUpdate,
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -31,13 +31,6 @@ class _EmojisState extends State<Emojis> {
|
|||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Text(
|
||||
('Select Emoji'),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
height: 315,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:camerawesome/camerawesome_plugin.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/src/components/image_editor/action_button.dart';
|
||||
import 'package:twonly/src/components/media_view_sizing.dart';
|
||||
import 'package:twonly/src/components/permissions_view.dart';
|
||||
import 'package:twonly/src/views/camera_to_share/share_image_editor_view.dart';
|
||||
|
|
@ -173,22 +174,22 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
BottomButton(
|
||||
icon: FontAwesomeIcons.repeat,
|
||||
onTap: () async {
|
||||
ActionButton(
|
||||
FontAwesomeIcons.repeat,
|
||||
onPressed: () async {
|
||||
cameraState.switchCameraSensor(
|
||||
aspectRatio:
|
||||
CameraAspectRatios.ratio_16_9);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
BottomButton(
|
||||
icon: FontAwesomeIcons.bolt,
|
||||
// SizedBox(height: 20),
|
||||
ActionButton(
|
||||
FontAwesomeIcons.bolt,
|
||||
color: isFlashOn
|
||||
? const Color.fromARGB(255, 255, 230, 0)
|
||||
: const Color.fromARGB(
|
||||
158, 255, 255, 255),
|
||||
onTap: () async {
|
||||
onPressed: () async {
|
||||
if (isFlashOn) {
|
||||
cameraState.sensorConfig
|
||||
.setFlashMode(FlashMode.none);
|
||||
|
|
|
|||
|
|
@ -63,26 +63,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
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(DrawLayerData());
|
||||
|
||||
// setState(() {});
|
||||
// }
|
||||
},
|
||||
),
|
||||
BottomButton(
|
||||
|
|
@ -156,7 +139,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
|
||||
double widthRatio = 1, heightRatio = 1, pixelRatio = 1;
|
||||
|
||||
/// obtain image Uint8List by merging layers
|
||||
Future<Uint8List?> getMergedImage() async {
|
||||
Uint8List? image;
|
||||
|
||||
|
|
@ -165,8 +147,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
} 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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue