move image editor to share image view

This commit is contained in:
otsmr 2025-02-02 19:04:19 +01:00
parent 4667ec09cc
commit d72f0abfc0
5 changed files with 578 additions and 583 deletions

View file

@ -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<Layer> layers = [];
List<Layer> undoLayers = [];
List<Layer> 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<ImageEditor> {
ImageItem currentImage = ImageItem();
ScreenshotController screenshotController = ScreenshotController();
@override
void dispose() {
layers.clear();
super.dispose();
}
List<Widget> get filterActions {
return [
IconButton(
icon: FaIcon(
FontAwesomeIcons.xmark,
size: 30,
shadows: <Shadow>[
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<Uint8List?> 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: <Widget>[
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<void> 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<ImageEditorDrawing> createState() => _ImageEditorDrawingState();
}
class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
Color pickerColor = Colors.white, currentColor = Colors.white;
var screenshotController = ScreenshotController();
final control = HandSignatureControl(
threshold: 3.0,
smoothRatio: 0.65,
velocityRange: 2.0,
);
List<CubicPath> 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: <Widget>[
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,
),
),
),
);
}
}

View file

@ -24,7 +24,7 @@ class _BackgroundLayerState extends State<BackgroundLayer> {
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),
);

View file

@ -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,
),
),
),
),
],
),
);
}
}

View file

@ -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<CameraPreviewView> {
double _lastZoom = 1;
double _basePanY = 0;
bool sharePreviewIsShown = false;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
@ -54,11 +55,11 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
@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<CameraPreviewView> {
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<CameraPreviewView> {
reverseTransitionDuration: Duration.zero,
),
);
setState(() {
sharePreviewIsShown = false;
});
},
multiple: (multiple) {
multiple.fileBySensor.forEach((key, value) {
@ -117,8 +124,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
}
},
builder: (cameraState, preview) {
return Container(
child: Stack(
return Stack(
alignment: Alignment.bottomCenter,
children: [
Positioned.fill(
@ -147,6 +153,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
},
),
),
if (!sharePreviewIsShown)
Positioned(
bottom: 30,
left: 0,
@ -187,7 +194,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
),
),
],
),
);
},
saveConfig: SaveConfig.photoAndVideo(

View file

@ -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<Layer> layers = [];
List<Layer> undoLayers = [];
List<Layer> removedLayers = [];
class ShareImageEditorView extends StatefulWidget {
const ShareImageEditorView({super.key, required this.imageBytes});
@ -18,40 +28,237 @@ class ShareImageEditorView extends StatefulWidget {
class _ShareImageEditorView extends State<ShareImageEditorView> {
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<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),
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<Uint8List?> 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<void> 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: [
MediaViewSizing(
SizedBox(
height: currentImage.height / pixelRatio,
width: currentImage.width / pixelRatio,
child: Screenshot(
controller: screenshotController,
child: LayersViewer(
layers: layers,
onUpdate: () {
setState(() {});
},
editable: true,
),
),
),
),
Positioned(
top: 0,
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: <Widget>[
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.symmetric(vertical: 0),
child: ClipRRect(
borderRadius: BorderRadius.circular(22),
// child: Container(),
child: ImageEditor(
image: widget.imageBytes,
),
),
),
),
Positioned(
bottom: 70,
left: 0,
right: 0,
padding: const EdgeInsets.only(bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -68,8 +275,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
: 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<ShareImageEditorView> {
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<ShareImageEditorView> {
],
),
),
],
),
),
);
}
}
/// 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<ImageEditorDrawing> createState() => _ImageEditorDrawingState();
}
class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
Color pickerColor = Colors.white, currentColor = Colors.white;
var screenshotController = ScreenshotController();
final control = HandSignatureControl(
threshold: 3.0,
smoothRatio: 0.65,
velocityRange: 2.0,
);
List<CubicPath> 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: <Widget>[
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,
),
),
),
);
}